aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--ci/azure-steps.yml4
-rw-r--r--ciimage/Dockerfile2
-rw-r--r--docs/markdown/FAQ.md87
-rw-r--r--mesonbuild/ast/printer.py3
-rw-r--r--mesonbuild/ast/visitor.py1
-rwxr-xr-xmesonbuild/cmake/data/run_ctgt.py59
-rw-r--r--mesonbuild/cmake/interpreter.py335
-rw-r--r--mesonbuild/cmake/traceparser.py140
-rw-r--r--mesonbuild/compilers/clike.py4
-rw-r--r--mesonbuild/dependencies/base.py6
-rw-r--r--mesonbuild/dependencies/boost.py29
-rw-r--r--mesonbuild/interpreter.py19
-rw-r--r--mesonbuild/scripts/meson_exe.py5
-rwxr-xr-xrun_meson_command_tests.py2
-rwxr-xr-xrun_project_tests.py4
-rwxr-xr-xrun_tests.py18
-rwxr-xr-xrun_unittests.py13
-rw-r--r--setup.py5
-rw-r--r--test cases/cmake/8 custom command/main.cpp11
-rw-r--r--test cases/cmake/8 custom command/meson.build12
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/CMakeLists.txt46
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/cmMod.cpp17
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/cmMod.hpp14
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/cp.cpp17
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.cpp.am5
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.hpp.am5
-rw-r--r--test cases/cmake/8 custom command/subprojects/cmMod/main.cpp30
-rw-r--r--test cases/frameworks/1 boost/meson.build53
-rw-r--r--test cases/frameworks/1 boost/python_module.cpp22
-rw-r--r--test cases/frameworks/1 boost/test_python_module.py27
31 files changed, 929 insertions, 70 deletions
diff --git a/.travis.yml b/.travis.yml
index 7658fa0..8f393f1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@ compiler:
env:
- MESON_ARGS=""
- - MESON_ARGS="--unity=on"
+ - RUN_TESTS_ARGS="--no-unittests" MESON_ARGS="--unity=on"
language:
- cpp
@@ -63,4 +63,4 @@ script:
/bin/sh -c "cd /root && mkdir -p tools; wget -c http://nirbheek.in/files/binaries/ninja/linux-amd64/ninja -O /root/tools/ninja; chmod +x /root/tools/ninja; CC=$CC CXX=$CXX OBJC=$CC OBJCXX=$CXX PATH=/root/tools:$PATH MESON_FIXED_NINJA=1 ./run_tests.py $RUN_TESTS_ARGS -- $MESON_ARGS && chmod -R a+rwX .coverage"
fi
# Ensure that llvm is added after $PATH, otherwise the clang from that llvm install will be used instead of the native apple clang.
- - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib OBJC=$CC OBJCXX=$CXX PATH=$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin MESON_FIXED_NINJA=1 ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib OBJC=$CC OBJCXX=$CXX PATH=$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin MESON_FIXED_NINJA=1 ./run_tests.py $RUN_TESTS_ARGS --backend=ninja -- $MESON_ARGS ; fi
diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml
index 77f61fe..36e6fb4 100644
--- a/ci/azure-steps.yml
+++ b/ci/azure-steps.yml
@@ -154,6 +154,10 @@ steps:
where.exe python
python --version
+ # Needed for running unit tests in parallel.
+ python -m pip install --upgrade pytest-xdist
+
+
echo ""
echo "Locating cl, rc:"
where.exe cl
diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile
index d5f4816..585a6ef 100644
--- a/ciimage/Dockerfile
+++ b/ciimage/Dockerfile
@@ -7,6 +7,7 @@ ENV DC=gdc
RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \
&& apt-get -y update && apt-get -y upgrade \
&& apt-get -y build-dep meson \
+&& apt-get -y install python3-pytest-xdist \
&& apt-get -y install python3-pip libxml2-dev libxslt1-dev cmake libyaml-dev \
&& python3 -m pip install hotdoc codecov \
&& apt-get -y install wget unzip \
@@ -22,6 +23,7 @@ RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \
&& apt-get -y install libgcrypt11-dev \
&& apt-get -y install libgpgme-dev \
&& apt-get -y install libhdf5-dev \
+&& apt-get -y install libboost-python-dev \
&& dub fetch urld && dub build urld --compiler=gdc \
&& dub fetch dubtestproject \
&& dub build dubtestproject:test1 --compiler=ldc2 \
diff --git a/docs/markdown/FAQ.md b/docs/markdown/FAQ.md
index e5b7a9c..06379ae 100644
--- a/docs/markdown/FAQ.md
+++ b/docs/markdown/FAQ.md
@@ -402,3 +402,90 @@ the form `foo.lib` when building with MSVC, you can set the
kwarg to `''` and the [`name_suffix:`](https://mesonbuild.com/Reference-manual.html#library)
kwarg to `'lib'`. To get the default behaviour for each, you can either not
specify the kwarg, or pass `[]` (an empty array) to it.
+
+## Do I need to add my headers to the sources list like in Autotools?
+
+Autotools requires you to add private and public headers to the sources list so
+that it knows what files to include in the tarball generated by `make dist`.
+Meson's `dist` command simply gathers everything committed to your git/hg
+repository and adds it to the tarball, so adding headers to the sources list is
+pointless.
+
+Meson uses Ninja which uses compiler dependency information to automatically
+figure out dependencies between C sources and headers, so it will rebuild
+things correctly when a header changes.
+
+The only exception to this are generated headers, for which you must [declare
+dependencies correctly](#how-do-i-tell-meson-that-my-sources-use-generated-headers).
+
+If, for whatever reason, you do add non-generated headers to the sources list
+of a target, Meson will simply ignore them.
+
+## How do I tell Meson that my sources use generated headers?
+
+Let's say you use a [`custom_target()`](https://mesonbuild.com/Reference-manual.html#custom_target)
+to generate the headers, and then `#include` them in your C code. Here's how
+you ensure that Meson generates the headers before trying to compile any
+sources in the build target:
+
+```meson
+libfoo_gen_headers = custom_target('gen-headers', ..., output: 'foo-gen.h')
+libfoo_sources = files('foo-utils.c', 'foo-lib.c')
+# Add generated headers to the list of sources for the build target
+libfoo = library('foo', sources: libfoo_sources + libfoo_gen_headers)
+```
+
+Now let's say you have a new target that links to `libfoo`:
+
+```meson
+libbar_sources = files('bar-lib.c')
+libbar = library('bar', sources: libbar_sources, link_with: libfoo)
+```
+
+This adds a **link-time** dependency between the two targets, but note that the
+sources of the targets have **no compile-time** dependencies and can be built
+in any order; which improves parallelism and speeds up builds.
+
+If the sources in `libbar` *also* use `foo-gen.h`, that's a *compile-time*
+dependency, and you'll have to add `libfoo_gen_headers` to `sources:` for
+`libbar` too:
+
+```meson
+libbar_sources = files('bar-lib.c')
+libbar = library('bar', sources: libbar_sources + libfoo_gen_headers, link_with: libfoo)
+```
+
+Alternatively, if you have multiple libraries with sources that link to
+a library and also use its generated headers, this code is equivalent to above:
+
+```meson
+# Add generated headers to the list of sources for the build target
+libfoo = library('foo', sources: libfoo_sources + libfoo_gen_headers)
+
+# Declare a dependency that will add the generated headers to sources
+libfoo_dep = declare_dependency(link_with: libfoo, sources: libfoo_gen_headers)
+
+...
+
+libbar = library('bar', sources: libbar_sources, dependencies: libfoo_dep)
+```
+
+**Note:** You should only add *headers* to `sources:` while declaring
+a dependency. If your custom target outputs both sources and headers, you can
+use the subscript notation to get only the header(s):
+
+```meson
+libfoo_gen_sources = custom_target('gen-headers', ..., output: ['foo-gen.h', 'foo-gen.c'])
+libfoo_gen_headers = libfoo_gen_sources[0]
+
+# Add static and generated sources to the target
+libfoo = library('foo', sources: libfoo_sources + libfoo_gen_sources)
+
+# Declare a dependency that will add the generated *headers* to sources
+libfoo_dep = declare_dependency(link_with: libfoo, sources: libfoo_gen_headers)
+...
+libbar = library('bar', sources: libbar_sources, dependencies: libfoo_dep)
+```
+
+A good example of a generator that outputs both sources and headers is
+[`gnome.mkenums()`](https://mesonbuild.com/Gnome-module.html#gnomemkenums).
diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py
index 2de1d0c..c6fb91a 100644
--- a/mesonbuild/ast/printer.py
+++ b/mesonbuild/ast/printer.py
@@ -118,6 +118,7 @@ class AstPrinter(AstVisitor):
self.newline()
def visit_IndexNode(self, node: mparser.IndexNode):
+ node.iobject.accept(self)
self.append('[', node)
node.index.accept(self)
self.append(']', node)
@@ -181,7 +182,7 @@ class AstPrinter(AstVisitor):
def visit_ArgumentNode(self, node: mparser.ArgumentNode):
break_args = (len(node.arguments) + len(node.kwargs)) > self.arg_newline_cutoff
for i in node.arguments + list(node.kwargs.values()):
- if not isinstance(i, mparser.ElementaryNode):
+ if not isinstance(i, (mparser.ElementaryNode, mparser.IndexNode)):
break_args = True
if break_args:
self.newline()
diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py
index fab4ed2..de13dae 100644
--- a/mesonbuild/ast/visitor.py
+++ b/mesonbuild/ast/visitor.py
@@ -84,6 +84,7 @@ class AstVisitor:
def visit_IndexNode(self, node: mparser.IndexNode):
self.visit_default_func(node)
+ node.iobject.accept(self)
node.index.accept(self)
def visit_MethodNode(self, node: mparser.MethodNode):
diff --git a/mesonbuild/cmake/data/run_ctgt.py b/mesonbuild/cmake/data/run_ctgt.py
new file mode 100755
index 0000000..0a9b80d
--- /dev/null
+++ b/mesonbuild/cmake/data/run_ctgt.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+import argparse
+import subprocess
+import shutil
+import os
+import sys
+
+commands = [[]]
+SEPERATOR = ';;;'
+
+# Generate CMD parameters
+parser = argparse.ArgumentParser(description='Wrapper for add_custom_command')
+parser.add_argument('-d', '--directory', type=str, metavar='D', required=True, help='Working directory to cwd to')
+parser.add_argument('-o', '--outputs', nargs='+', metavar='O', required=True, help='Expected output files')
+parser.add_argument('-O', '--original-outputs', nargs='+', metavar='O', required=True, help='Output files expected by CMake')
+parser.add_argument('commands', nargs=argparse.REMAINDER, help='A "{}" seperated list of commands'.format(SEPERATOR))
+
+# Parse
+args = parser.parse_args()
+
+if len(args.outputs) != len(args.original_outputs):
+ print('Length of output list and original output list differ')
+ sys.exit(1)
+
+for i in args.commands:
+ if i == SEPERATOR:
+ commands += [[]]
+ continue
+
+ commands[-1] += [i]
+
+# Execute
+for i in commands:
+ # Skip empty lists
+ if not i:
+ continue
+
+ subprocess.run(i, cwd=args.directory)
+
+# Copy outputs
+zipped_outputs = zip(args.outputs, args.original_outputs)
+for expected, generated in zipped_outputs:
+ do_copy = False
+ if not os.path.exists(expected):
+ if not os.path.exists(generated):
+ print('Unable to find generated file. This can cause the build to fail:')
+ print(generated)
+ do_copy = False
+ else:
+ do_copy = True
+ elif os.path.exists(generated):
+ if os.path.getmtime(generated) > os.path.getmtime(expected):
+ do_copy = True
+
+ if do_copy:
+ if os.path.exists(expected):
+ os.remove(expected)
+ shutil.copyfile(generated, expected)
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py
index 88700f1..28a8488 100644
--- a/mesonbuild/cmake/interpreter.py
+++ b/mesonbuild/cmake/interpreter.py
@@ -18,15 +18,33 @@
from .common import CMakeException
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, CMakeTarget
from .executor import CMakeExecutor
+from .traceparser import CMakeTraceParser, CMakeGeneratorTarget
from .. import mlog
from ..environment import Environment
from ..mesonlib import MachineChoice
-from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode, MethodNode
-from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes
-from subprocess import Popen, PIPE, STDOUT
-from typing import List, Dict, Optional, TYPE_CHECKING
+from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, is_header
+from subprocess import Popen, PIPE
+from typing import Any, List, Dict, Optional, TYPE_CHECKING
+from threading import Thread
import os, re
+from ..mparser import (
+ Token,
+ BaseNode,
+ CodeBlockNode,
+ FunctionNode,
+ ArrayNode,
+ ArgumentNode,
+ AssignmentNode,
+ BooleanNode,
+ StringNode,
+ IdNode,
+ IndexNode,
+ MethodNode,
+ NumberNode,
+)
+
+
if TYPE_CHECKING:
from ..build import Build
from ..backend.backends import Backend
@@ -87,6 +105,13 @@ blacklist_link_libs = [
'advapi32.lib'
]
+# Utility functions to generate local keys
+def _target_key(tgt_name: str) -> str:
+ return '__tgt_{}__'.format(tgt_name)
+
+def _generated_file_key(fname: str) -> str:
+ return '__gen_{}__'.format(os.path.basename(fname))
+
class ConverterTarget:
lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()}
@@ -184,11 +209,19 @@ class ConverterTarget:
temp += [i]
self.link_libraries = temp
+ # Filter out files that are not supported by the language
+ supported = list(header_suffixes) + list(obj_suffixes)
+ for i in self.languages:
+ supported += list(lang_suffixes[i])
+ supported = ['.{}'.format(x) for x in supported]
+ self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])]
+ self.generated = [x for x in self.generated if any([x.endswith(y) for y in supported])]
+
# Make paths relative
- def rel_path(x: str, is_header: bool) -> Optional[str]:
+ def rel_path(x: str, is_header: bool, is_generated: bool) -> Optional[str]:
if not os.path.isabs(x):
x = os.path.normpath(os.path.join(self.src_dir, x))
- if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]):
+ if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated:
mlog.warning('CMake: path', mlog.bold(x), 'does not exist. Ignoring. This can lead to build errors')
return None
if os.path.isabs(x) and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir():
@@ -200,23 +233,29 @@ class ConverterTarget:
return os.path.relpath(x, root_src_dir)
return x
+ def custom_target(x: str):
+ key = _generated_file_key(x)
+ if key in output_target_map:
+ ctgt = output_target_map[key]
+ assert(isinstance(ctgt, ConverterCustomTarget))
+ ref = ctgt.get_ref(x)
+ assert(isinstance(ref, CustomTargetReference) and ref.valid())
+ return ref
+ 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, True) for x in set(self.includes)] + [build_dir_rel]))
- self.sources = [rel_path(x, False) for x in self.sources]
- self.generated = [rel_path(x, False) for x in self.generated]
+ self.includes = list(set([rel_path(x, True, False) for x in set(self.includes)] + [build_dir_rel]))
+ self.sources = [rel_path(x, False, False) for x in self.sources]
+ self.generated = [rel_path(x, False, True) for x in self.generated]
+
+ # Resolve custom targets
+ self.generated = [custom_target(x) for x in self.generated]
+ # Remove delete entries
self.includes = [x for x in self.includes if x is not None]
self.sources = [x for x in self.sources if x is not None]
self.generated = [x for x in self.generated if x is not None]
- # Filter out files that are not supported by the language
- supported = list(header_suffixes) + list(obj_suffixes)
- for i in self.languages:
- supported += list(lang_suffixes[i])
- supported = ['.{}'.format(x) for x in supported]
- self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])]
- self.generated = [x for x in self.generated if any([x.endswith(y) for y in supported])]
-
# Make sure '.' is always in the include directories
if '.' not in self.includes:
self.includes += ['.']
@@ -239,7 +278,8 @@ class ConverterTarget:
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 self.generated if isinstance(x, str)]
+ temp = [os.path.basename(x) for x in temp]
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
@@ -251,7 +291,7 @@ class ConverterTarget:
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])]
+ self.generated = [x for x in self.generated if not isinstance(x, str) or not any([x.endswith('.' + y) for y in obj_suffixes])]
def meson_func(self) -> str:
return target_type_map.get(self.type.upper())
@@ -277,6 +317,113 @@ class ConverterTarget:
for key, val in self.compile_opts.items():
mlog.log(' -', key, '=', mlog.bold(str(val)))
+class CustomTargetReference:
+ def __init__(self, ctgt: 'ConverterCustomTarget', index: int):
+ self.ctgt = ctgt # type: ConverterCustomTarget
+ self.index = index # type: int
+
+ def __repr__(self) -> str:
+ if self.valid():
+ return '<{}: {} [{}]>'.format(self.__class__.__name__, self.ctgt.name, self.ctgt.outputs[self.index])
+ else:
+ return '<{}: INVALID REFERENCE>'.format(self.__class__.__name__)
+
+ def valid(self) -> bool:
+ return self.ctgt is not None and self.index >= 0
+
+ def filename(self) -> str:
+ return self.ctgt.outputs[self.index]
+
+class ConverterCustomTarget:
+ tgt_counter = 0 # type: int
+
+ def __init__(self, target: CMakeGeneratorTarget):
+ self.name = 'custom_tgt_{}'.format(ConverterCustomTarget.tgt_counter)
+ self.original_outputs = list(target.outputs)
+ self.outputs = [os.path.basename(x) for x in self.original_outputs]
+ self.command = target.command
+ self.working_dir = target.working_dir
+ self.depends_raw = target.depends
+ self.inputs = []
+ self.depends = []
+
+ ConverterCustomTarget.tgt_counter += 1
+
+ def __repr__(self) -> str:
+ return '<{}: {}>'.format(self.__class__.__name__, self.outputs)
+
+ def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, build_dir: str) -> None:
+ # Default the working directory to the CMake build dir. This
+ # is not 100% correct, since it should be the value of
+ # ${CMAKE_CURRENT_BINARY_DIR} when add_custom_command is
+ # called. However, keeping track of this variable is not
+ # trivial and the current solution should work in most cases.
+ if not self.working_dir:
+ self.working_dir = build_dir
+
+ # relative paths in the working directory are always relative
+ # to ${CMAKE_CURRENT_BINARY_DIR} (see note above)
+ if not os.path.isabs(self.working_dir):
+ self.working_dir = os.path.normpath(os.path.join(build_dir, self.working_dir))
+
+ # Modify the original outputs if they are relative. Again,
+ # relative paths are relative to ${CMAKE_CURRENT_BINARY_DIR}
+ # and the first disclaimer is stil in effect
+ def ensure_absolute(x: str):
+ if os.path.isabs(x):
+ return x
+ else:
+ return os.path.normpath(os.path.join(build_dir, x))
+ self.original_outputs = [ensure_absolute(x) for x in self.original_outputs]
+
+ # Check if the command is a build target
+ commands = []
+ for i in self.command:
+ assert(isinstance(i, list))
+ cmd = []
+
+ for j in i:
+ target_key = _target_key(j)
+ if target_key in output_target_map:
+ cmd += [output_target_map[target_key]]
+ else:
+ cmd += [j]
+
+ commands += [cmd]
+ self.command = commands
+
+ # Check dependencies and input files
+ for i in self.depends_raw:
+ tgt_key = _target_key(i)
+ gen_key = _generated_file_key(i)
+
+ if os.path.basename(i) in output_target_map:
+ self.depends += [output_target_map[os.path.basename(i)]]
+ elif tgt_key in output_target_map:
+ self.depends += [output_target_map[tgt_key]]
+ elif gen_key in output_target_map:
+ self.inputs += [output_target_map[gen_key].get_ref(i)]
+ elif not os.path.isabs(i) and os.path.exists(os.path.join(root_src_dir, i)):
+ self.inputs += [i]
+ elif os.path.isabs(i) and os.path.exists(i) and os.path.commonpath([i, root_src_dir]) == root_src_dir:
+ self.inputs += [os.path.relpath(i, root_src_dir)]
+
+ def get_ref(self, fname: str) -> Optional[CustomTargetReference]:
+ try:
+ idx = self.outputs.index(os.path.basename(fname))
+ return CustomTargetReference(self, idx)
+ except ValueError:
+ return None
+
+ def log(self) -> None:
+ mlog.log('Custom Target', mlog.bold(self.name))
+ mlog.log(' -- command: ', mlog.bold(str(self.command)))
+ mlog.log(' -- outputs: ', mlog.bold(str(self.outputs)))
+ mlog.log(' -- working_dir: ', mlog.bold(str(self.working_dir)))
+ mlog.log(' -- depends_raw: ', mlog.bold(str(self.depends_raw)))
+ mlog.log(' -- inputs: ', mlog.bold(str(self.inputs)))
+ mlog.log(' -- depends: ', mlog.bold(str(self.depends)))
+
class CMakeInterpreter:
def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: Environment, backend: 'Backend'):
assert(hasattr(backend, 'name'))
@@ -293,11 +440,14 @@ class CMakeInterpreter:
# Raw CMake results
self.bs_files = []
self.codemodel = None
+ self.raw_trace = None
# Analysed data
self.project_name = ''
self.languages = []
self.targets = []
+ self.custom_targets = [] # type: List[ConverterCustomTarget]
+ self.trace = CMakeTraceParser()
# Generated meson data
self.generated_targets = {}
@@ -327,6 +477,7 @@ class CMakeInterpreter:
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 += ['--trace', '--trace-expand']
cmake_args += extra_cmake_options
# Run CMake
@@ -338,17 +489,25 @@ class CMakeInterpreter:
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)
+ proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=PIPE, 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'))
+ def print_stdout():
+ while True:
+ line = proc.stdout.readline()
+ if not line:
+ break
+ mlog.log(line.decode('utf-8').strip('\n'))
+ proc.stdout.close()
- # Wait for CMake to finish
- proc.communicate()
+ t = Thread(target=print_stdout)
+ t.start()
+
+ self.raw_trace = proc.stderr.read()
+ self.raw_trace = self.raw_trace.decode('utf-8')
+ proc.stderr.close()
+ proc.wait()
+
+ t.join()
mlog.log()
h = mlog.green('SUCCEEDED') if proc.returncode == 0 else mlog.red('FAILED')
@@ -391,6 +550,11 @@ class CMakeInterpreter:
self.project_name = ''
self.languages = []
self.targets = []
+ self.custom_targets = []
+ self.trace = CMakeTraceParser(permissive=True)
+
+ # Parse the trace
+ self.trace.parse(self.raw_trace)
# Find all targets
for i in self.codemodel.configs:
@@ -401,13 +565,24 @@ class CMakeInterpreter:
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.trace.custom_targets:
+ self.custom_targets += [ConverterCustomTarget(i)]
+
+ # generate the output_target_map
+ output_target_map = {}
+ output_target_map.update({x.full_name: x for x in self.targets})
+ output_target_map.update({_target_key(x.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
+ for i in self.custom_targets:
+ for j in i.original_outputs:
+ output_target_map[_generated_file_key(j)] = i
object_libs = []
# First pass: Basic target cleanup
+ for i in self.custom_targets:
+ i.postprocess(output_target_map, self.src_dir, self.subdir, self.build_dir)
for i in self.targets:
i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix)
if i.type == 'OBJECT_LIBRARY':
@@ -418,7 +593,7 @@ class CMakeInterpreter:
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.')
+ mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.')
def pretend_to_be_meson(self) -> CodeBlockNode:
if not self.project_name:
@@ -433,15 +608,23 @@ class CMakeInterpreter:
def id_node(value: str) -> IdNode:
return IdNode(token(val=value))
+ def number(value: int) -> NumberNode:
+ return NumberNode(token(val=value))
+
def nodeify(value):
if isinstance(value, str):
return string(value)
elif isinstance(value, bool):
return BooleanNode(token(), value)
+ elif isinstance(value, int):
+ return number(value)
elif isinstance(value, list):
return array(value)
return value
+ def indexed(node: BaseNode, index: int) -> IndexNode:
+ return IndexNode(node, nodeify(index))
+
def array(elements) -> ArrayNode:
args = ArgumentNode(token())
if not isinstance(elements, list):
@@ -480,12 +663,30 @@ class CMakeInterpreter:
# Generate the root code block and the project function call
root_cb = CodeBlockNode(token())
root_cb.lines += [function('project', [self.project_name] + self.languages)]
+
+ # Add the run script for custom commands
+ run_script = '{}/data/run_ctgt.py'.format(os.path.dirname(os.path.realpath(__file__)))
+ run_script_var = 'ctgt_run_script'
+ root_cb.lines += [assign(run_script_var, function('find_program', [[run_script]], {'required': True}))]
+
+ # Add the targets
processed = {}
+ def resolve_ctgt_ref(ref: CustomTargetReference) -> BaseNode:
+ tgt_var = processed[ref.ctgt.name]['tgt']
+ if len(ref.ctgt.outputs) == 1:
+ return id_node(tgt_var)
+ else:
+ return indexed(id_node(tgt_var), ref.index)
+
def process_target(tgt: ConverterTarget):
# First handle inter target dependencies
link_with = []
objec_libs = []
+ sources = []
+ generated = []
+ generated_filenames = []
+ custom_targets = []
for i in tgt.link_with:
assert(isinstance(i, ConverterTarget))
if i.name not in processed:
@@ -497,6 +698,32 @@ class CMakeInterpreter:
process_target(i)
objec_libs += [processed[i.name]['tgt']]
+ # Generate the source list and handle generated sources
+ for i in tgt.sources + tgt.generated:
+ if isinstance(i, CustomTargetReference):
+ if i.ctgt.name not in processed:
+ process_custom_target(i.ctgt)
+ generated += [resolve_ctgt_ref(i)]
+ generated_filenames += [i.filename()]
+ if i.ctgt not in custom_targets:
+ custom_targets += [i.ctgt]
+ else:
+ sources += [i]
+
+ # Add all header files from all used custom targets. This
+ # ensures that all custom targets are built before any
+ # sources of the current target are compiled and thus all
+ # header files are present. This step is necessary because
+ # CMake always ensures that a custom target is executed
+ # before another target if at least one output is used.
+ for i in custom_targets:
+ for j in i.outputs:
+ if not is_header(j) or j in generated_filenames:
+ continue
+
+ generated += [resolve_ctgt_ref(i.get_ref(j))]
+ generated_filenames += [j]
+
# Determine the meson function to use for the build target
tgt_func = tgt.meson_func()
if not tgt_func:
@@ -540,15 +767,59 @@ class CMakeInterpreter:
# 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))
+ src_node = assign(src_var, function('files', sources))
+ tgt_node = assign(tgt_var, function(tgt_func, [base_name, [id_node(src_var)] + generated], 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}
+ def process_custom_target(tgt: ConverterCustomTarget) -> None:
+ # CMake allows to specify multiple commands in a custom target.
+ # To map this to meson, a helper script is used to execute all
+ # commands in order. This addtionally allows setting the working
+ # directory.
+
+ tgt_var = tgt.name # type: str
+
+ def resolve_source(x: Any) -> Any:
+ if isinstance(x, ConverterTarget):
+ if x.name not in processed:
+ process_target(x)
+ return id_node(x.name)
+ elif isinstance(x, CustomTargetReference):
+ if x.ctgt.name not in processed:
+ process_custom_target(x.ctgt)
+ return resolve_ctgt_ref(x)
+ else:
+ return x
+
+ # Generate the command list
+ command = []
+ command += [id_node(run_script_var)]
+ command += ['-o', '@OUTPUT@']
+ command += ['-O'] + tgt.original_outputs
+ command += ['-d', tgt.working_dir]
+
+ # Generate the commands. Subcommands are seperated by ';;;'
+ for cmd in tgt.command:
+ command += [resolve_source(x) for x in cmd] + [';;;']
+
+ tgt_kwargs = {
+ 'input': [resolve_source(x) for x in tgt.inputs],
+ 'output': tgt.outputs,
+ 'command': command,
+ 'depends': [resolve_source(x) for x in tgt.depends],
+ }
+
+ root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))]
+ processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'}
+
# Now generate the target function calls
+ for i in self.custom_targets:
+ if i.name not in processed:
+ process_custom_target(i)
for i in self.targets:
if i.name not in processed:
process_target(i)
diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py
index 1dcf6d2..4b87319 100644
--- a/mesonbuild/cmake/traceparser.py
+++ b/mesonbuild/cmake/traceparser.py
@@ -16,9 +16,11 @@
# or an interpreter-based tool.
from .common import CMakeException
+from .. import mlog
-from typing import List, Tuple
+from typing import List, Tuple, Optional
import re
+import os
class CMakeTraceLine:
def __init__(self, file, line, func, args):
@@ -46,14 +48,26 @@ class CMakeTarget:
propSTR += " '{}': {}\n".format(i, self.properies[i])
return s.format(self.name, self.type, propSTR)
-class CMakeTraceParser:
+class CMakeGeneratorTarget:
def __init__(self):
+ self.outputs = [] # type: List[str]
+ self.command = [] # type: List[List[str]]
+ self.working_dir = None # type: Optional[str]
+ self.depends = [] # type: List[str]
+
+class CMakeTraceParser:
+ def __init__(self, permissive: bool = False):
# Dict of CMake variables: '<var_name>': ['list', 'of', 'values']
self.vars = {}
# Dict of CMakeTarget
self.targets = {}
+ # List of targes that were added with add_custom_command to generate files
+ self.custom_targets = [] # type: List[CMakeGeneratorTarget]
+
+ self.permissive = permissive # type: bool
+
def parse(self, trace: str) -> None:
# First parse the trace
lexer1 = self._lex_trace(trace)
@@ -64,6 +78,7 @@ class CMakeTraceParser:
'unset': self._cmake_unset,
'add_executable': self._cmake_add_executable,
'add_library': self._cmake_add_library,
+ 'add_custom_command': self._cmake_add_custom_command,
'add_custom_target': self._cmake_add_custom_target,
'set_property': self._cmake_set_property,
'set_target_properties': self._cmake_set_target_properties
@@ -102,6 +117,14 @@ class CMakeTraceParser:
return True
return False
+ def _gen_exception(self, function: str, error: str, tline: CMakeTraceLine) -> None:
+ # Generate an exception if the parser is not in permissive mode
+
+ if self.permissive:
+ mlog.debug('CMake trace warning: {}() {}\n{}'.format(function, error, tline))
+ return None
+ raise CMakeException('CMake: {}() {}\n{}'.format(function, error, tline))
+
def _cmake_set(self, tline: CMakeTraceLine) -> None:
"""Handler for the CMake set() function in all variaties.
@@ -132,7 +155,7 @@ class CMakeTraceParser:
args.append(i)
if len(args) < 1:
- raise CMakeException('CMake: set() requires at least one argument\n{}'.format(tline))
+ return self._gen_exception('set', 'requires at least one argument', tline)
# Now that we've removed extra arguments all that should be left is the
# variable identifier and the value, join the value back together to
@@ -151,7 +174,7 @@ class CMakeTraceParser:
def _cmake_unset(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/unset.html
if len(tline.args) < 1:
- raise CMakeException('CMake: unset() requires at least one argument\n{}'.format(tline))
+ return self._gen_exception('unset', 'requires at least one argument', tline)
if tline.args[0] in self.vars:
del self.vars[tline.args[0]]
@@ -162,12 +185,12 @@ class CMakeTraceParser:
# Make sure the exe is imported
if 'IMPORTED' not in args:
- raise CMakeException('CMake: add_executable() non imported executables are not supported\n{}'.format(tline))
+ return self._gen_exception('add_executable', 'non imported executables are not supported', tline)
args.remove('IMPORTED')
if len(args) < 1:
- raise CMakeException('CMake: add_executable() requires at least 1 argument\n{}'.format(tline))
+ return self._gen_exception('add_executable', 'requires at least 1 argument', tline)
self.targets[args[0]] = CMakeTarget(args[0], 'EXECUTABLE', {})
@@ -177,21 +200,82 @@ class CMakeTraceParser:
# Make sure the lib is imported
if 'IMPORTED' not in args:
- raise CMakeException('CMake: add_library() non imported libraries are not supported\n{}'.format(tline))
+ return self._gen_exception('add_library', 'non imported libraries are not supported', tline)
args.remove('IMPORTED')
# No only look at the first two arguments (target_name and target_type) and ignore the rest
if len(args) < 2:
- raise CMakeException('CMake: add_library() requires at least 2 arguments\n{}'.format(tline))
+ return self._gen_exception('add_library', 'requires at least 2 arguments', tline)
self.targets[args[0]] = CMakeTarget(args[0], args[1], {})
+ def _cmake_add_custom_command(self, tline: CMakeTraceLine):
+ # DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html
+ args = list(tline.args) # Make a working copy
+
+ if not args:
+ return self._gen_exception('add_custom_command', 'requires at least 1 argument', tline)
+
+ # Skip the second function signature
+ if args[0] == 'TARGET':
+ return self._gen_exception('add_custom_command', 'TARGET syntax is currently not supported', tline)
+
+ magic_keys = ['OUTPUT', 'COMMAND', 'MAIN_DEPENDENCY', 'DEPENDS', 'BYPRODUCTS',
+ 'IMPLICIT_DEPENDS', 'WORKING_DIRECTORY', 'COMMENT', 'DEPFILE',
+ 'JOB_POOL', 'VERBATIM', 'APPEND', 'USES_TERMINAL', 'COMMAND_EXPAND_LISTS']
+
+ target = CMakeGeneratorTarget()
+
+ def handle_output(key: str, target: CMakeGeneratorTarget) -> None:
+ target.outputs += [key]
+
+ def handle_command(key: str, target: CMakeGeneratorTarget) -> None:
+ if key == 'ARGS':
+ return
+ target.command[-1] += [key]
+
+ def handle_depends(key: str, target: CMakeGeneratorTarget) -> None:
+ target.depends += [key]
+
+ def handle_working_dir(key: str, target: CMakeGeneratorTarget) -> None:
+ if target.working_dir is None:
+ target.working_dir = key
+ else:
+ target.working_dir += ' '
+ target.working_dir += key
+
+ fn = None
+
+ for i in args:
+ if i in magic_keys:
+ if i == 'OUTPUT':
+ fn = handle_output
+ elif i == 'DEPENDS':
+ fn = handle_depends
+ elif i == 'WORKING_DIRECTORY':
+ fn = handle_working_dir
+ elif i == 'COMMAND':
+ fn = handle_command
+ target.command += [[]]
+ else:
+ fn = None
+ continue
+
+ if fn is not None:
+ fn(i, target)
+
+ target.outputs = self._guess_files(target.outputs)
+ target.depends = self._guess_files(target.depends)
+ target.command = [self._guess_files(x) for x in target.command]
+
+ self.custom_targets += [target]
+
def _cmake_add_custom_target(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_target.html
# We only the first parameter (the target name) is interesting
if len(tline.args) < 1:
- raise CMakeException('CMake: add_custom_target() requires at least one argument\n{}'.format(tline))
+ return self._gen_exception('add_custom_target', 'requires at least one argument', tline)
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {})
@@ -219,7 +303,7 @@ class CMakeTraceParser:
targets.append(curr)
if not args:
- raise CMakeException('CMake: set_property() faild to parse argument list\n{}'.format(tline))
+ return self._gen_exception('set_property', 'faild to parse argument list', tline)
if len(args) == 1:
# Tries to set property to nothing so nothing has to be done
@@ -232,7 +316,7 @@ class CMakeTraceParser:
for i in targets:
if i not in self.targets:
- raise CMakeException('CMake: set_property() TARGET {} not found\n{}'.format(i, tline))
+ return self._gen_exception('set_property', 'TARGET {} not found'.format(i), tline)
if identifier not in self.targets[i].properies:
self.targets[i].properies[identifier] = []
@@ -284,7 +368,7 @@ class CMakeTraceParser:
for name, value in arglist:
for i in targets:
if i not in self.targets:
- raise CMakeException('CMake: set_target_properties() TARGET {} not found\n{}'.format(i, tline))
+ return self._gen_exception('set_target_properties', 'TARGET {} not found'.format(i), tline)
self.targets[i].properies[name] = value
@@ -315,3 +399,35 @@ class CMakeTraceParser:
args = list(map(lambda x: reg_genexp.sub('', x), args)) # Remove generator expressions
yield CMakeTraceLine(file, line, func, args)
+
+ def _guess_files(self, broken_list: List[str]) -> List[str]:
+ #Try joining file paths that contain spaces
+
+ reg_start = re.compile(r'^([A-Za-z]:)?/.*/[^./]+$')
+ reg_end = re.compile(r'^.*\.[a-zA-Z]+$')
+
+ fixed_list = [] # type: List[str]
+ curr_str = None # type: Optional[str]
+
+ for i in broken_list:
+ if curr_str is None:
+ curr_str = i
+ elif os.path.isfile(curr_str):
+ # Abort concatination if curr_str is an existing file
+ fixed_list += [curr_str]
+ curr_str = i
+ elif not reg_start.match(curr_str):
+ # Abort concatination if curr_str no longer matches the regex
+ fixed_list += [curr_str]
+ curr_str = i
+ elif reg_end.match(i):
+ # File detected
+ curr_str = '{} {}'.format(curr_str, i)
+ fixed_list += [curr_str]
+ curr_str = None
+ else:
+ curr_str = '{} {}'.format(curr_str, i)
+
+ if curr_str:
+ fixed_list += [curr_str]
+ return fixed_list
diff --git a/mesonbuild/compilers/clike.py b/mesonbuild/compilers/clike.py
index 6d540e7..046ffba 100644
--- a/mesonbuild/compilers/clike.py
+++ b/mesonbuild/compilers/clike.py
@@ -387,8 +387,8 @@ class CLikeCompiler:
def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False, temp_dir=None):
args = self._get_compiler_check_args(env, extra_args, dependencies, mode)
if disable_cache or want_output:
- return self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=temp_dir)
- return self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=temp_dir)
+ return self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir)
+ return self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir)
def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False):
return self.compiles(code, env, extra_args=extra_args,
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
index fdfd143..8616367 100644
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -1357,7 +1357,11 @@ class CMakeDependency(ExternalDependency):
if 'RELEASE' in cfgs:
cfg = 'RELEASE'
- if 'IMPORTED_LOCATION_{}'.format(cfg) in tgt.properies:
+ if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properies:
+ libraries += tgt.properies['IMPORTED_IMPLIB_{}'.format(cfg)]
+ elif 'IMPORTED_IMPLIB' in tgt.properies:
+ libraries += tgt.properies['IMPORTED_IMPLIB']
+ elif 'IMPORTED_LOCATION_{}'.format(cfg) in tgt.properies:
libraries += tgt.properies['IMPORTED_LOCATION_{}'.format(cfg)]
elif 'IMPORTED_LOCATION' in tgt.properies:
libraries += tgt.properies['IMPORTED_LOCATION']
diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py
index 5c9e0b5..340a5a9 100644
--- a/mesonbuild/dependencies/boost.py
+++ b/mesonbuild/dependencies/boost.py
@@ -134,22 +134,31 @@ class BoostDependency(ExternalDependency):
else:
self.incdir = self.detect_nix_incdir()
- if self.check_invalid_modules():
- return
-
mlog.debug('Boost library root dir is', mlog.bold(self.boost_root))
mlog.debug('Boost include directory is', mlog.bold(self.incdir))
# 1. check if we can find BOOST headers.
self.detect_headers_and_version()
+ if not self.is_found:
+ return # if we can not find 'boost/version.hpp'
+
# 2. check if we can find BOOST libraries.
- if self.is_found:
- self.detect_lib_modules()
- mlog.debug('Boost library directory is', mlog.bold(self.libdir))
+ self.detect_lib_modules()
+ mlog.debug('Boost library directory is', mlog.bold(self.libdir))
+
+ mlog.debug('Installed Boost libraries: ')
+ for key in sorted(self.lib_modules.keys()):
+ mlog.debug(key, self.lib_modules[key])
+
+ # 3. check if requested modules are valid, that is, either found or in the list of known boost libraries
+ self.check_invalid_modules()
+
+ # 4. final check whether or not we find all requested and valid modules
+ self.check_find_requested_modules()
def check_invalid_modules(self):
- invalid_modules = [c for c in self.requested_modules if 'boost_' + c not in BOOST_LIBS]
+ invalid_modules = [c for c in self.requested_modules if 'boost_' + c not in self.lib_modules and 'boost_' + c not in BOOST_LIBS]
# previous versions of meson allowed include dirs as modules
remove = []
@@ -273,6 +282,7 @@ class BoostDependency(ExternalDependency):
else:
self.detect_lib_modules_nix()
+ def check_find_requested_modules(self):
# 3. Check if we can find the modules
for m in self.requested_modules:
if 'boost_' + m not in self.lib_modules:
@@ -491,7 +501,6 @@ class BoostDependency(ExternalDependency):
def get_sources(self):
return []
-
# Generated with boost_names.py
BOOST_LIBS = [
'boost_atomic',
@@ -547,10 +556,6 @@ BOOST_LIBS = [
'boost_math_c99l',
'boost_mpi',
'boost_program_options',
- 'boost_python',
- 'boost_python3',
- 'boost_numpy',
- 'boost_numpy3',
'boost_random',
'boost_regex',
'boost_serialization',
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 7b7c100..fd94251 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -3869,6 +3869,23 @@ different subdirectory.
for_machine = self.machine_from_native_kwarg(kwargs)
self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs)
+ def warn_about_builtin_args(self, args):
+ warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic')
+ optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os')
+ for arg in args:
+ if arg in warnargs:
+ mlog.warning("Consider using the builtin warning_level option instead of adding warning flags by hand.")
+ elif arg in optargs:
+ mlog.warning('Consider using the builtin optimization level rather than adding flags by hand.')
+ elif arg == '-g':
+ mlog.warning('Consider using the builtin debug option rather than adding flags by hand.')
+ elif arg == '-pipe':
+ mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.")
+ elif arg.startswith('-fsanitize'):
+ mlog.warning('Consider using the builtin option for sanitizers rather than adding flags by hand.')
+ elif arg.startswith('-std=') or arg.startswith('/std:'):
+ mlog.warning('Consider using the builtin option for language standard version rather than adding flags by hand.')
+
def add_global_arguments(self, node, argsdict, args, kwargs):
if self.is_subproject():
msg = 'Function \'{}\' cannot be used in subprojects because ' \
@@ -3897,6 +3914,8 @@ different subdirectory.
if 'language' not in kwargs:
raise InvalidCode('Missing language definition in {}'.format(node.func_name))
+ self.warn_about_builtin_args(args)
+
for lang in mesonlib.stringlistify(kwargs['language']):
lang = lang.lower()
argsdict[lang] = argsdict.get(lang, []) + args
diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py
index 85dfe99..3fe327f 100644
--- a/mesonbuild/scripts/meson_exe.py
+++ b/mesonbuild/scripts/meson_exe.py
@@ -78,6 +78,11 @@ def run_exe(exe):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
+
+ if p.returncode == 0xc0000135:
+ # STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose
+ raise FileNotFoundError('Missing DLLs on calling {!r}'.format(exe.name))
+
if exe.capture and p.returncode == 0:
with open(exe.capture, 'wb') as output:
output.write(stdout)
diff --git a/run_meson_command_tests.py b/run_meson_command_tests.py
index e7eab72..9dfb62e 100755
--- a/run_meson_command_tests.py
+++ b/run_meson_command_tests.py
@@ -142,6 +142,8 @@ class CommandTests(unittest.TestCase):
s = p.as_posix()
if 'mesonbuild' not in s:
continue
+ if '/data/' in s:
+ continue
have.add(s[s.rfind('mesonbuild'):])
self.assertEqual(have, expect)
# Run `meson`
diff --git a/run_project_tests.py b/run_project_tests.py
index 3bd3253..154da00 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -803,6 +803,8 @@ def check_format():
for (root, _, files) in os.walk('.'):
if '.dub' in root: # external deps are here
continue
+ if '.pytest_cache' in root:
+ continue
if 'meson-logs' in root or 'meson-private' in root:
continue
for fname in files:
@@ -870,6 +872,8 @@ if __name__ == '__main__':
choices=backendlist)
parser.add_argument('--failfast', action='store_true',
help='Stop running if test case fails')
+ parser.add_argument('--no-unittests', action='store_true',
+ help='Not used, only here to simplify run_tests.py')
parser.add_argument('--only', help='name of test(s) to run', nargs='+')
options = parser.parse_args()
setup_commands(options.backend)
diff --git a/run_tests.py b/run_tests.py
index f427736..051b91e 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -262,6 +262,7 @@ def main():
choices=backendlist)
parser.add_argument('--cross', default=False, dest='cross', action='store_true')
parser.add_argument('--failfast', action='store_true')
+ parser.add_argument('--no-unittests', action='store_true', default=False)
(options, _) = parser.parse_known_args()
# Enable coverage early...
enable_coverage = options.cov
@@ -273,6 +274,7 @@ def main():
returncode = 0
cross = options.cross
backend, _ = guess_backend(options.backend, shutil.which('msbuild'))
+ no_unittests = options.no_unittests
# Running on a developer machine? Be nice!
if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'CI' not in os.environ:
os.nice(20)
@@ -314,12 +316,16 @@ def main():
returncode += subprocess.call(cmd, env=env)
if options.failfast and returncode != 0:
return returncode
- cmd = mesonlib.python_command + ['run_unittests.py', '-v']
- if options.failfast:
- cmd += ['--failfast']
- returncode += subprocess.call(cmd, env=env)
- if options.failfast and returncode != 0:
- return returncode
+ if no_unittests:
+ print('Skipping all unit tests.')
+ returncode = 0
+ else:
+ cmd = mesonlib.python_command + ['run_unittests.py', '-v']
+ if options.failfast:
+ cmd += ['--failfast']
+ returncode += subprocess.call(cmd, env=env)
+ if options.failfast and returncode != 0:
+ return returncode
cmd = mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:]
returncode += subprocess.call(cmd, env=env)
else:
diff --git a/run_unittests.py b/run_unittests.py
index c5a6d4f..56f2683 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -3398,7 +3398,7 @@ recommended as it is not supported on some platforms''')
for entry in res:
name = entry['name']
- self.assertEquals(entry['subproject'], expected[name])
+ self.assertEqual(entry['subproject'], expected[name])
def test_introspect_projectinfo_subproject_dir(self):
testdir = os.path.join(self.common_test_dir, '79 custom subproject dir')
@@ -6493,6 +6493,17 @@ def unset_envs():
def main():
unset_envs()
+ pytest_args = ['-n', 'auto', './run_unittests.py']
+ if shutil.which('pytest-3'):
+ return subprocess.run(['pytest-3'] + pytest_args).returncode
+ elif shutil.which('pytest'):
+ return subprocess.run(['pytest'] + pytest_args).returncode
+ try:
+ import pytest # noqa: F401
+ return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode
+ except ImportError:
+ pass
+ # All attempts at locating pytest failed, fall back to plain unittest.
cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests',
'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests',
'TAPParserTests',
diff --git a/setup.py b/setup.py
index 7a7edef..0a2efb1 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,10 @@ packages = ['mesonbuild',
'mesonbuild.modules',
'mesonbuild.scripts',
'mesonbuild.wrap']
-package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakeListsLLVM.txt', 'data/CMakePathInfo.txt']}
+package_data = {
+ 'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakeListsLLVM.txt', 'data/CMakePathInfo.txt'],
+ 'mesonbuild.cmake': ['data/run_ctgt.py'],
+}
data_files = []
if sys.platform != 'win32':
# Only useful on UNIX-like systems
diff --git a/test cases/cmake/8 custom command/main.cpp b/test cases/cmake/8 custom command/main.cpp
new file mode 100644
index 0000000..fa6b0fa
--- /dev/null
+++ b/test cases/cmake/8 custom command/main.cpp
@@ -0,0 +1,11 @@
+#include <iostream>
+#include <cmMod.hpp>
+
+using namespace std;
+
+int main() {
+ cmModClass obj("Hello");
+ cout << obj.getStr() << endl;
+ cout << obj.getOther() << endl;
+ return 0;
+}
diff --git a/test cases/cmake/8 custom command/meson.build b/test cases/cmake/8 custom command/meson.build
new file mode 100644
index 0000000..799e339
--- /dev/null
+++ b/test cases/cmake/8 custom command/meson.build
@@ -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_type('cmModLib') == 'shared_library', 'Target type should be shared_library')
+assert(sub_pro.target_type('gen') == 'executable', 'Target type should be executable')
+
+exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep])
+test('test1', exe1)
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/8 custom command/subprojects/cmMod/CMakeLists.txt
new file mode 100644
index 0000000..259151c
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/CMakeLists.txt
@@ -0,0 +1,46 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(cmMod)
+set (CMAKE_CXX_STANDARD 14)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+add_definitions("-DDO_NOTHING_JUST_A_FLAG=1")
+
+add_executable(gen main.cpp)
+add_executable(mycpy cp.cpp)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/genTest.cpp" "${CMAKE_CURRENT_BINARY_DIR}/genTest.hpp"
+ COMMAND gen ARGS genTest
+)
+
+add_custom_command(
+ OUTPUT cpyBase.cpp
+ COMMAND mycpy "${CMAKE_CURRENT_SOURCE_DIR}/cpyBase.cpp.am" cpyBase.cpp.in
+ COMMAND mycpy cpyBase.cpp.in cpyBase.cpp.something
+ COMMAND mycpy cpyBase.cpp.something cpyBase.cpp.IAmRunningOutOfIdeas
+ COMMAND mycpy cpyBase.cpp.IAmRunningOutOfIdeas cpyBase.cpp
+ DEPENDS cpyBase.cpp.am gen
+)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/cpyBase.hpp.in"
+ COMMAND mycpy "${CMAKE_CURRENT_SOURCE_DIR}/cpyBase.hpp.am" cpyBase.hpp.in
+ DEPENDS cpyBase.hpp.am
+)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/cpyBase.hpp.something"
+ COMMAND mycpy cpyBase.hpp.in cpyBase.hpp.something
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/cpyBase.hpp.in"
+)
+
+add_custom_command(
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/cpyBase.hpp"
+ COMMAND mycpy cpyBase.hpp.something cpyBase.hpp
+ DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/cpyBase.hpp.something"
+)
+
+add_library(cmModLib SHARED cmMod.cpp genTest.cpp cpyBase.cpp cpyBase.hpp)
+include(GenerateExportHeader)
+generate_export_header(cmModLib)
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.cpp b/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.cpp
new file mode 100644
index 0000000..0fb6aa7
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.cpp
@@ -0,0 +1,17 @@
+#include "cmMod.hpp"
+#include "genTest.hpp"
+#include "cpyBase.hpp"
+
+using namespace std;
+
+cmModClass::cmModClass(string foo) {
+ str = foo + " World";
+}
+
+string cmModClass::getStr() const {
+ return str;
+}
+
+string cmModClass::getOther() const {
+ return getStr() + " -- " + getStrCpy();
+}
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.hpp b/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.hpp
new file mode 100644
index 0000000..cfdbe88
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/cmMod.hpp
@@ -0,0 +1,14 @@
+#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;
+ std::string getOther() const;
+};
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/cp.cpp b/test cases/cmake/8 custom command/subprojects/cmMod/cp.cpp
new file mode 100644
index 0000000..2744da8
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/cp.cpp
@@ -0,0 +1,17 @@
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+int main(int argc, char *argv[]) {
+ if(argc < 3) {
+ cerr << argv[0] << " requires an input and an output file!" << endl;
+ return 1;
+ }
+
+ ifstream src(argv[1]);
+ ofstream dst(argv[2]);
+
+ dst << src.rdbuf();
+ return 0;
+}
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.cpp.am b/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.cpp.am
new file mode 100644
index 0000000..98dd09c
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.cpp.am
@@ -0,0 +1,5 @@
+#include "cpyBase.hpp"
+
+std::string getStrCpy() {
+ return "Hello Copied File";
+}
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.hpp.am b/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.hpp.am
new file mode 100644
index 0000000..c255fb1
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/cpyBase.hpp.am
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <string>
+
+std::string getStrCpy();
diff --git a/test cases/cmake/8 custom command/subprojects/cmMod/main.cpp b/test cases/cmake/8 custom command/subprojects/cmMod/main.cpp
new file mode 100644
index 0000000..9fade21
--- /dev/null
+++ b/test cases/cmake/8 custom command/subprojects/cmMod/main.cpp
@@ -0,0 +1,30 @@
+#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 out1(string(argv[1]) + ".hpp");
+ ofstream out2(string(argv[1]) + ".cpp");
+ out1 << R"(
+#pragma once
+
+#include <string>
+
+std::string getStr();
+)";
+
+ out2 << R"(
+#include ")" << argv[1] << R"(.hpp"
+
+std::string getStr() {
+ return "Hello World";
+}
+)";
+
+ return 0;
+}
diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build
index 1d29455..8f45dc7 100644
--- a/test cases/frameworks/1 boost/meson.build
+++ b/test cases/frameworks/1 boost/meson.build
@@ -26,18 +26,71 @@ testdep = dependency('boost', modules : ['unit_test_framework'])
nomoddep = dependency('boost')
extralibdep = dependency('boost', modules : ['thread', 'system', 'log_setup', 'log'])
+pymod = import('python')
+python2 = pymod.find_installation('python2', required: host_machine.system() == 'linux', disabler: true)
+python3 = pymod.find_installation('python3', required: host_machine.system() == 'linux', disabler: true)
+python2dep = python2.dependency(required: host_machine.system() == 'linux', disabler: true)
+python3dep = python3.dependency(required: host_machine.system() == 'linux', disabler: true)
+
+# compile python 2/3 modules only if we found a corresponding python version
+if(python2dep.found() and host_machine.system() == 'linux')
+ if(dep.version().version_compare('>=1.67'))
+ # if we have a new version of boost, we need to construct the module name based
+ # on the installed version of python (and hope that they match the version boost
+ # was compiled against)
+ py2version_string = ''.join(python2dep.version().split('.'))
+ bpython2dep = dependency('boost', modules : ['python' + py2version_string])
+ else
+ # if we have an older version of boost, we need to use the old module names
+ bpython2dep = dependency('boost', modules : ['python'])
+ endif
+
+ if not (bpython2dep.found())
+ bpython2dep = disabler()
+ endif
+else
+ python2dep = disabler()
+ bpython2dep = disabler()
+endif
+
+if(python3dep.found() and host_machine.system() == 'linux')
+ if(dep.version().version_compare('>=1.67'))
+ py3version_string = ''.join(python3dep.version().split('.'))
+ bpython3dep = dependency('boost', modules : ['python' + py3version_string])
+ else
+ bpython3dep = dependency('boost', modules : ['python3'])
+ endif
+
+ if not (bpython3dep.found())
+ bpython3dep = disabler()
+ endif
+else
+ python3dep = disabler()
+ bpython3dep = disabler()
+endif
+
linkexe = executable('linkedexe', 'linkexe.cc', dependencies : linkdep)
staticexe = executable('staticlinkedexe', 'linkexe.cc', dependencies : staticdep)
unitexe = executable('utf', 'unit_test.cpp', dependencies: testdep)
nomodexe = executable('nomod', 'nomod.cpp', dependencies : nomoddep)
extralibexe = executable('extralibexe', 'extralib.cpp', dependencies : extralibdep)
+# python modules are shared libraries
+python2module = shared_library('python2_module', ['python_module.cpp'], dependencies: [python2dep, bpython2dep], name_prefix: '', cpp_args: ['-DMOD_NAME=python2_module'])
+python3module = shared_library('python3_module', ['python_module.cpp'], dependencies: [python3dep, bpython3dep], name_prefix: '', cpp_args: ['-DMOD_NAME=python3_module'])
+
test('Boost linktest', linkexe)
test('Boost statictest', staticexe)
test('Boost UTF test', unitexe)
test('Boost nomod', nomodexe)
test('Boost extralib test', extralibexe)
+# explicitly use the correct python interpreter so that we don't have to provide two different python scripts that have different shebang lines
+python2interpreter = find_program(python2.path(), required: false, disabler: true)
+test('Boost Python2', python2interpreter, args: ['./test_python_module.py', meson.current_build_dir()], workdir: meson.current_source_dir(), depends: python2module)
+python3interpreter = find_program(python3.path(), required: false, disabler: true)
+test('Boost Python3', python3interpreter, args: ['./test_python_module.py', meson.current_build_dir()], workdir: meson.current_source_dir(), depends: python2module)
+
subdir('partial_dep')
# check we can apply a version constraint
diff --git a/test cases/frameworks/1 boost/python_module.cpp b/test cases/frameworks/1 boost/python_module.cpp
new file mode 100644
index 0000000..a0f010b
--- /dev/null
+++ b/test cases/frameworks/1 boost/python_module.cpp
@@ -0,0 +1,22 @@
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <boost/python.hpp>
+
+struct World
+{
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string version() { return std::to_string(PY_MAJOR_VERSION) + "." + std::to_string(PY_MINOR_VERSION); }
+ std::string msg;
+};
+
+
+BOOST_PYTHON_MODULE(MOD_NAME)
+{
+ using namespace boost::python;
+ class_<World>("World")
+ .def("greet", &World::greet)
+ .def("set", &World::set)
+ .def("version", &World::version)
+ ;
+}
diff --git a/test cases/frameworks/1 boost/test_python_module.py b/test cases/frameworks/1 boost/test_python_module.py
new file mode 100644
index 0000000..acf6e42
--- /dev/null
+++ b/test cases/frameworks/1 boost/test_python_module.py
@@ -0,0 +1,27 @@
+import sys
+sys.path.append(sys.argv[1])
+
+# import compiled python module depending on version of python we are running with
+if sys.version_info[0] == 2:
+ import python2_module
+
+if sys.version_info[0] == 3:
+ import python3_module
+
+
+def run():
+ msg = 'howdy'
+ if sys.version_info[0] == 2:
+ w = python2_module.World()
+
+ if sys.version_info[0] == 3:
+ w = python3_module.World()
+
+ w.set(msg)
+
+ assert(msg == w.greet())
+ version_string = str(sys.version_info[0]) + "." + str(sys.version_info[1])
+ assert(version_string == w.version())
+
+if __name__ == '__main__':
+ run()