aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mesonbuild/astinterpreter.py241
-rw-r--r--mesonbuild/backend/ninjabackend.py23
-rw-r--r--mesonbuild/build.py6
-rw-r--r--mesonbuild/compilers.py59
-rw-r--r--mesonbuild/dependencies.py25
-rw-r--r--mesonbuild/interpreter.py606
-rw-r--r--mesonbuild/interpreterbase.py636
-rw-r--r--mesonbuild/mesonlib.py29
-rw-r--r--mesonbuild/mesonmain.py4
-rw-r--r--mesonbuild/mintro.py5
-rw-r--r--mesonbuild/modules/gnome.py87
-rw-r--r--mesonbuild/modules/i18n.py62
-rw-r--r--mesonbuild/modules/pkgconfig.py1
-rw-r--r--mesonbuild/mparser.py127
-rw-r--r--mesonbuild/optinterpreter.py2
-rwxr-xr-xmesonrewriter.py64
-rwxr-xr-xmesontest.py128
-rwxr-xr-xrun_project_tests.py13
-rwxr-xr-xrun_unittests.py202
-rw-r--r--test cases/common/43 has function/meson.build32
-rw-r--r--test cases/common/51 pkgconfig-gen/meson.build16
-rw-r--r--test cases/failing/37 pkgconfig dependency impossible conditions/meson.build3
-rw-r--r--test cases/frameworks/6 gettext/data/meson.build8
-rw-r--r--test cases/frameworks/6 gettext/data/test.desktop.in6
-rw-r--r--test cases/frameworks/6 gettext/installed_files.txt1
-rw-r--r--test cases/frameworks/6 gettext/meson.build1
-rw-r--r--test cases/frameworks/6 gettext/po/LINGUAS2
-rw-r--r--test cases/frameworks/6 gettext/po/POTFILES1
-rw-r--r--test cases/frameworks/7 gnome/installed_files.txt2
-rw-r--r--test cases/frameworks/7 gnome/resources/meson.build11
-rw-r--r--test cases/frameworks/7 gnome/resources/resources.py10
-rw-r--r--test cases/linuxlike/1 pkg-config/meson.build11
-rw-r--r--test cases/linuxlike/1 pkg-config/prog-checkver.c15
-rw-r--r--test cases/linuxlike/5 dependency versions/meson.build11
-rw-r--r--test cases/rewrite/1 basic/added.txt5
-rw-r--r--test cases/rewrite/1 basic/meson.build5
-rw-r--r--test cases/rewrite/1 basic/removed.txt5
-rw-r--r--test cases/rewrite/2 subdirs/meson.build5
-rw-r--r--test cases/rewrite/2 subdirs/sub1/after.txt1
-rw-r--r--test cases/rewrite/2 subdirs/sub1/meson.build1
-rw-r--r--test cases/rewrite/2 subdirs/sub2/meson.build2
-rw-r--r--test cases/unit/1 soname/CMakeLists.txt26
-rw-r--r--test cases/unit/1 soname/meson.build18
-rw-r--r--test cases/unit/1 soname/versioned.c3
-rw-r--r--test cases/vala/10 mixed sources/c/foo.c (renamed from test cases/vala/11 mixed sources/c/foo.c)0
-rw-r--r--test cases/vala/10 mixed sources/c/meson.build (renamed from test cases/vala/11 mixed sources/c/meson.build)0
-rw-r--r--test cases/vala/10 mixed sources/c/writec.py (renamed from test cases/vala/11 mixed sources/c/writec.py)0
-rw-r--r--test cases/vala/10 mixed sources/meson.build (renamed from test cases/vala/11 mixed sources/meson.build)0
-rw-r--r--test cases/vala/10 mixed sources/vala/bar.vala (renamed from test cases/vala/11 mixed sources/vala/bar.vala)0
-rw-r--r--test cases/vala/11 generated vapi/installed_files.txt (renamed from test cases/vala/12 generated vapi/installed_files.txt)0
-rw-r--r--test cases/vala/11 generated vapi/libbar/bar.c (renamed from test cases/vala/12 generated vapi/libbar/bar.c)0
-rw-r--r--test cases/vala/11 generated vapi/libbar/bar.h (renamed from test cases/vala/12 generated vapi/libbar/bar.h)0
-rw-r--r--test cases/vala/11 generated vapi/libbar/meson.build (renamed from test cases/vala/12 generated vapi/libbar/meson.build)0
-rw-r--r--test cases/vala/11 generated vapi/libfoo/foo.c (renamed from test cases/vala/12 generated vapi/libfoo/foo.c)0
-rw-r--r--test cases/vala/11 generated vapi/libfoo/foo.h (renamed from test cases/vala/12 generated vapi/libfoo/foo.h)0
-rw-r--r--test cases/vala/11 generated vapi/libfoo/meson.build (renamed from test cases/vala/12 generated vapi/libfoo/meson.build)0
-rw-r--r--test cases/vala/11 generated vapi/main.vala (renamed from test cases/vala/12 generated vapi/main.vala)0
-rw-r--r--test cases/vala/11 generated vapi/meson.build (renamed from test cases/vala/12 generated vapi/meson.build)0
-rw-r--r--test cases/vala/12 custom output/foo.vala (renamed from test cases/vala/13 custom output/foo.vala)0
-rw-r--r--test cases/vala/12 custom output/meson.build (renamed from test cases/vala/13 custom output/meson.build)0
-rw-r--r--test cases/vala/13 find library/meson.build (renamed from test cases/vala/14 find library/meson.build)0
-rw-r--r--test cases/vala/13 find library/test.vala (renamed from test cases/vala/14 find library/test.vala)0
62 files changed, 1764 insertions, 757 deletions
diff --git a/mesonbuild/astinterpreter.py b/mesonbuild/astinterpreter.py
new file mode 100644
index 0000000..3691d64
--- /dev/null
+++ b/mesonbuild/astinterpreter.py
@@ -0,0 +1,241 @@
+# Copyright 2016 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 . import interpreterbase, mlog, mparser, mesonlib
+from . import environment
+
+from .interpreterbase import InterpreterException, InvalidArguments
+
+import os, sys
+
+class DontCareObject(interpreterbase.InterpreterObject):
+ pass
+
+class MockExecutable(interpreterbase.InterpreterObject):
+ pass
+
+class MockStaticLibrary(interpreterbase.InterpreterObject):
+ pass
+
+class MockSharedLibrary(interpreterbase.InterpreterObject):
+ pass
+
+class MockCustomTarget(interpreterbase.InterpreterObject):
+ pass
+
+class MockRunTarget(interpreterbase.InterpreterObject):
+ pass
+
+ADD_SOURCE = 0
+REMOVE_SOURCE = 1
+
+class AstInterpreter(interpreterbase.InterpreterBase):
+ def __init__(self, source_root, subdir):
+ super().__init__(source_root, subdir)
+ self.asts = {}
+ self.funcs.update({'project' : self.func_do_nothing,
+ 'test' : self.func_do_nothing,
+ 'benchmark' : self.func_do_nothing,
+ 'install_headers' : self.func_do_nothing,
+ 'install_man' : self.func_do_nothing,
+ 'install_data' : self.func_do_nothing,
+ 'install_subdir' : self.func_do_nothing,
+ 'configuration_data' : self.func_do_nothing,
+ 'configure_file' : self.func_do_nothing,
+ 'find_program' : self.func_do_nothing,
+ 'include_directories' : self.func_do_nothing,
+ 'add_global_arguments' : self.func_do_nothing,
+ 'add_global_link_arguments' : self.func_do_nothing,
+ 'add_project_arguments' : self.func_do_nothing,
+ 'add_project_link_arguments' : self.func_do_nothing,
+ 'message' : self.func_do_nothing,
+ 'generator' : self.func_do_nothing,
+ 'error' : self.func_do_nothing,
+ 'run_command' : self.func_do_nothing,
+ 'assert' : self.func_do_nothing,
+ 'subproject' : self.func_do_nothing,
+ 'dependency' : self.func_do_nothing,
+ 'get_option' : self.func_do_nothing,
+ 'join_paths' : self.func_do_nothing,
+ 'environment' : self.func_do_nothing,
+ 'import' : self.func_do_nothing,
+ 'vcs_tag' : self.func_do_nothing,
+ 'add_languages' : self.func_do_nothing,
+ 'declare_dependency' : self.func_do_nothing,
+ 'files' : self.func_files,
+ 'executable': self.func_executable,
+ 'static_library' : self.func_static_lib,
+ 'shared_library' : self.func_shared_lib,
+ 'library' : self.func_library,
+ 'build_target' : self.func_build_target,
+ 'custom_target' : self.func_custom_target,
+ 'run_target' : self.func_run_target,
+ 'subdir' : self.func_subdir,
+ 'set_variable' : self.func_set_variable,
+ 'get_variable' : self.func_get_variable,
+ 'is_variable' : self.func_is_variable,
+ })
+
+ def func_do_nothing(self, node, args, kwargs):
+ return True
+
+ def method_call(self, node):
+ return True
+
+ def func_executable(self, node, args, kwargs):
+ if args[0] == self.targetname:
+ if self.operation == ADD_SOURCE:
+ self.add_source_to_target(node, args, kwargs)
+ elif self.operation == REMOVE_SOURCE:
+ self.remove_source_from_target(node, args, kwargs)
+ else:
+ raise NotImplementedError('Bleep bloop')
+ return MockExecutable()
+
+ def func_static_lib(self, node, args, kwargs):
+ return MockStaticLibrary()
+
+ def func_shared_lib(self, node, args, kwargs):
+ return MockSharedLibrary()
+
+ def func_library(self, node, args, kwargs):
+ return self.func_shared_lib(node, args, kwargs)
+
+ def func_custom_target(self, node, args, kwargs):
+ return MockCustomTarget()
+
+ def func_run_target(self, node, args, kwargs):
+ return MockRunTarget()
+
+ def func_subdir(self, node, args, kwargs):
+ prev_subdir = self.subdir
+ subdir = os.path.join(prev_subdir, args[0])
+ self.subdir = subdir
+ buildfilename = os.path.join(self.subdir, environment.build_filename)
+ absname = os.path.join(self.source_root, buildfilename)
+ if not os.path.isfile(absname):
+ self.subdir = prev_subdir
+ raise InterpreterException('Nonexistant build def file %s.' % buildfilename)
+ with open(absname, encoding='utf8') as f:
+ code = f.read()
+ assert(isinstance(code, str))
+ try:
+ codeblock = mparser.Parser(code, self.subdir).parse()
+ self.asts[subdir] = codeblock
+ except mesonlib.MesonException as me:
+ me.file = buildfilename
+ raise me
+ self.evaluate_codeblock(codeblock)
+ self.subdir = prev_subdir
+
+ def func_files(self, node, args, kwargs):
+ if not isinstance(args, list):
+ return [args]
+ return args
+
+ def evaluate_arithmeticstatement(self, cur):
+ return 0
+
+ def evaluate_plusassign(self, node):
+ return 0
+
+ def evaluate_indexing(self, node):
+ return 0
+
+ def reduce_arguments(self, args):
+ assert(isinstance(args, mparser.ArgumentNode))
+ if args.incorrect_order():
+ raise InvalidArguments('All keyword arguments must be after positional arguments.')
+ return (args.arguments, args.kwargs)
+
+ def transform(self):
+ self.load_root_meson_file()
+ self.asts[''] = self.ast
+ self.sanity_check_ast()
+ self.parse_project()
+ self.run()
+
+ def add_source(self, targetname, filename):
+ self.operation = ADD_SOURCE
+ self.targetname = targetname
+ self.filename = filename
+ self.transform()
+
+ def remove_source(self, targetname, filename):
+ self.operation = REMOVE_SOURCE
+ self.targetname = targetname
+ self.filename = filename
+ self.transform()
+
+ def unknown_function_called(self, func_name):
+ mlog.warning('Unknown function called: ' + func_name)
+
+ def add_source_to_target(self, node, args, kwargs):
+ namespan = node.args.arguments[0].bytespan
+ buildfilename = os.path.join(self.source_root, self.subdir, environment.build_filename)
+ raw_data = open(buildfilename, 'r').read()
+ updated = raw_data[0:namespan[1]] + (", '%s'" % self.filename) + raw_data[namespan[1]:]
+ open(buildfilename, 'w').write(updated)
+ sys.exit(0)
+
+ def remove_argument_item(self, args, i):
+ assert(isinstance(args, mparser.ArgumentNode))
+ namespan = args.arguments[i].bytespan
+ # Usually remove the comma after this item but if it is
+ # the last argument, we need to remove the one before.
+ if i >= len(args.commas):
+ i -= 1
+ if i < 0:
+ commaspan = (0, 0) # Removed every entry in the list.
+ else:
+ commaspan = args.commas[i].bytespan
+ if commaspan[0] < namespan[0]:
+ commaspan, namespan = namespan, commaspan
+ buildfilename = os.path.join(self.source_root, args.subdir, environment.build_filename)
+ raw_data = open(buildfilename, 'r').read()
+ intermediary = raw_data[0:commaspan[0]] + raw_data[commaspan[1]:]
+ updated = intermediary[0:namespan[0]] + intermediary[namespan[1]:]
+ open(buildfilename, 'w').write(updated)
+ sys.exit(0)
+
+ def hacky_find_and_remove(self, node_to_remove):
+ for a in self.asts[node_to_remove.subdir].lines:
+ if a.lineno == node_to_remove.lineno:
+ if isinstance(a, mparser.AssignmentNode):
+ v = a.value
+ if not isinstance(v, mparser.ArrayNode):
+ raise NotImplementedError('Not supported yet, bro.')
+ args = v.args
+ for i in range(len(args.arguments)):
+ if isinstance(args.arguments[i], mparser.StringNode) and self.filename == args.arguments[i].value:
+ self.remove_argument_item(args, i)
+ raise NotImplementedError('Sukkess')
+
+ def remove_source_from_target(self, node, args, kwargs):
+ for i in range(1, len(node.args)):
+ # Is file name directly in function call as a string.
+ if isinstance(node.args.arguments[i], mparser.StringNode) and self.filename == node.args.arguments[i].value:
+ self.remove_argument_item(node.args, i)
+ # Is file name in a variable that gets expanded here.
+ if isinstance(node.args.arguments[i], mparser.IdNode):
+ avar = self.get_variable(node.args.arguments[i].value)
+ if not isinstance(avar, list):
+ raise NotImplementedError('Non-arrays not supported yet, sorry.')
+ for entry in avar:
+ if isinstance(entry, mparser.StringNode) and entry.value == self.filename:
+ self.hacky_find_and_remove(entry)
+ sys.exit('Could not find source %s in target %s.' % (self.filename, args[0]))
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 659a53d..71797ed 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -715,7 +715,7 @@ int dummy;
meson_exe = self.environment.get_build_command()
(base, ext) = os.path.splitext(meson_exe)
test_exe = base + 'test' + ext
- cmd = [sys.executable, test_exe]
+ cmd = [sys.executable, test_exe, '--no-rebuild']
if not self.environment.coredata.get_builtin_option('stdsplit'):
cmd += ['--no-stdsplit']
if self.environment.coredata.get_builtin_option('errorlogs'):
@@ -728,7 +728,7 @@ int dummy;
# And then benchmarks.
cmd = [sys.executable, test_exe, '--benchmark','--logbase',
- 'benchmarklog', '--num-processes=1']
+ 'benchmarklog', '--num-processes=1', '--no-rebuild']
elem = NinjaBuildElement(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY'])
elem.add_item('COMMAND', cmd)
elem.add_item('DESC', 'Running benchmark suite.')
@@ -1017,9 +1017,11 @@ int dummy;
args += valac.get_werror_args()
for d in target.get_external_deps():
if isinstance(d, dependencies.PkgConfigDependency):
- if d.name == 'glib-2.0' and d.version_requirement is not None \
- and d.version_requirement.startswith(('>=', '==')):
- args += ['--target-glib', d.version_requirement[2:]]
+ if d.name == 'glib-2.0' and d.version_reqs is not None:
+ for req in d.version_reqs:
+ if req.startswith(('>=', '==')):
+ args += ['--target-glib', req[2:]]
+ break
args += ['--pkg', d.name]
elif isinstance(d, dependencies.ExternalLibrary):
args += d.get_lang_args('vala')
@@ -2004,14 +2006,21 @@ rule FORTRAN_DEP_HACK
def generate_shlib_aliases(self, target, outdir):
basename = target.get_filename()
aliases = target.get_aliaslist()
- for alias in aliases:
+ for i, alias in enumerate(aliases):
aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
try:
os.remove(aliasfile)
except Exception:
pass
+ # If both soversion and version are set and to different values,
+ # the .so symlink must point to the soversion symlink rather than the
+ # original file.
+ if i == 0 and len(aliases) > 1:
+ pointed_to_filename = aliases[1]
+ else:
+ pointed_to_filename = basename
try:
- os.symlink(basename, aliasfile)
+ os.symlink(pointed_to_filename, aliasfile)
except NotImplementedError:
mlog.debug("Library versioning disabled because symlinks are not supported.")
except OSError:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 39e215f..462a55b 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -1069,12 +1069,10 @@ class SharedLibrary(BuildTarget):
self.soversion = str(self.soversion)
if not isinstance(self.soversion, str):
raise InvalidArguments('Shared library soversion is not a string or integer.')
- try:
- int(self.soversion)
- except ValueError:
- raise InvalidArguments('Shared library soversion must be a valid integer')
elif self.ltversion:
# library version is defined, get the soversion from that
+ # We replicate what Autotools does here and take the first
+ # number of the version by default.
self.soversion = self.ltversion.split('.')[0]
# Visual Studio module-definitions file
if 'vs_module_defs' in kwargs:
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py
index 8f8851f..2534a47 100644
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -701,8 +701,28 @@ int main () {{
#endif
return 0;
}}'''
- args = extra_args + self.get_compiler_check_args()
- return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies)
+ return self.compiles(templ.format(hname, symbol, prefix), env,
+ extra_args, dependencies)
+
+ @staticmethod
+ def _override_args(args, override):
+ '''
+ Add @override to @args in such a way that arguments are overriden
+ correctly.
+
+ We want the include directories to be added first (since they are
+ chosen left-to-right) and all other arguments later (since they
+ override previous arguments or add to a list that's chosen
+ right-to-left).
+ '''
+ before_args = []
+ after_args = []
+ for arg in override:
+ if arg.startswith(('-I', '/I')):
+ before_args.append(arg)
+ else:
+ after_args.append(arg)
+ return before_args + args + after_args
def compiles(self, code, env, extra_args=None, dependencies=None):
if extra_args is None:
@@ -713,9 +733,10 @@ int main () {{
dependencies = []
elif not isinstance(dependencies, list):
dependencies = [dependencies]
+ # Add compile flags needed by dependencies after converting to the
+ # native type of the selected compiler
cargs = [a for d in dependencies for a in d.get_compile_args()]
- # Convert flags to the native type of the selected compiler
- args = self.unix_link_flags_to_native(cargs + extra_args)
+ args = self.unix_link_flags_to_native(cargs)
# Read c_args/cpp_args/etc from the cross-info file (if needed)
args += self.get_cross_extra_flags(env, compile=True, link=False)
# Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env
@@ -723,6 +744,11 @@ int main () {{
args += env.coredata.external_args[self.language]
# We only want to compile; not link
args += self.get_compile_only_args()
+ # Append extra_args to the compiler check args such that it overrides
+ extra_args = self._override_args(self.get_compiler_check_args(), extra_args)
+ extra_args = self.unix_link_flags_to_native(extra_args)
+ # Append both to the compiler args such that they override them
+ args = self._override_args(args, extra_args)
with self.compile(code, args) as p:
return p.returncode == 0
@@ -736,17 +762,24 @@ int main () {{
dependencies = []
elif not isinstance(dependencies, list):
dependencies = [dependencies]
+ # Add compile and link flags needed by dependencies after converting to
+ # the native type of the selected compiler
cargs = [a for d in dependencies for a in d.get_compile_args()]
link_args = [a for d in dependencies for a in d.get_link_args()]
- # Convert flags to the native type of the selected compiler
- args = self.unix_link_flags_to_native(cargs + link_args + extra_args)
+ args = self.unix_link_flags_to_native(cargs + link_args)
# Select a CRT if needed since we're linking
args += self.get_linker_debug_crt_args()
- # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the cross-info file (if needed)
+ # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the
+ # cross-info file (if needed)
args += self.get_cross_extra_flags(env, compile=True, link=True)
# Add LDFLAGS from the env. We assume that the user has ensured these
# are compiler-specific
args += env.coredata.external_link_args[self.language]
+ # Append extra_args to the compiler check args such that it overrides
+ extra_args = self._override_args(self.get_compiler_check_args(), extra_args)
+ extra_args = self.unix_link_flags_to_native(extra_args)
+ # Append both to the compiler args such that they override them
+ args = self._override_args(args, extra_args)
return self.compile(code, args)
def links(self, code, env, extra_args=None, dependencies=None):
@@ -795,7 +828,6 @@ int main(int argc, char **argv) {{
%s
int temparray[%d-sizeof(%s)];
'''
- args = extra_args + self.get_compiler_check_args()
if not self.compiles(element_exists_templ.format(prefix, element), env, args, dependencies):
return -1
for i in range(1, 1024):
@@ -844,7 +876,6 @@ struct tmp {
int testarray[%d-offsetof(struct tmp, target)];
'''
- args = extra_args + self.get_compiler_check_args()
if not self.compiles(type_exists_templ.format(typename), env, args, dependencies):
return -1
for i in range(1, 1024):
@@ -980,14 +1011,14 @@ int main(int argc, char **argv) {
head, main = self._no_prototype_templ()
templ = head + stubs_fail + main
- args = extra_args + self.get_compiler_check_args()
- if self.links(templ.format(prefix, funcname), env, args, dependencies):
+ if self.links(templ.format(prefix, funcname), env, extra_args, dependencies):
return True
# Some functions like alloca() are defined as compiler built-ins which
# are inlined by the compiler, so test for that instead. Built-ins are
# special functions that ignore all includes and defines, so we just
# directly try to link via main().
- return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), env, args, dependencies)
+ return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname),
+ env, extra_args, dependencies)
def has_members(self, typename, membernames, prefix, env, extra_args=None, dependencies=None):
if extra_args is None:
@@ -1071,8 +1102,8 @@ class CPPCompiler(CCompiler):
#include <{0}>
using {1};
int main () {{ return 0; }}'''
- args = extra_args + self.get_compiler_check_args()
- return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies)
+ return self.compiles(templ.format(hname, symbol, prefix), env,
+ extra_args, dependencies)
class ObjCCompiler(CCompiler):
def __init__(self, exelist, version, is_cross, exe_wrap):
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py
index 4e87e4e..a092732 100644
--- a/mesonbuild/dependencies.py
+++ b/mesonbuild/dependencies.py
@@ -23,7 +23,7 @@ import re
import os, stat, glob, subprocess, shutil
import sysconfig
from collections import OrderedDict
-from . mesonlib import MesonException
+from . mesonlib import MesonException, version_compare, version_compare_many
from . import mlog
from . import mesonlib
from .environment import detect_cpu_family, for_windows
@@ -135,22 +135,27 @@ class PkgConfigDependency(Dependency):
self.modversion = 'none'
return
found_msg = ['%s dependency' % self.type_string, mlog.bold(name), 'found:']
- self.version_requirement = kwargs.get('version', None)
- if self.version_requirement is None:
+ self.version_reqs = kwargs.get('version', None)
+ if self.version_reqs is None:
self.is_found = True
else:
- if not isinstance(self.version_requirement, str):
- raise DependencyException('Version argument must be string.')
- self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement)
+ if not isinstance(self.version_reqs, (str, list)):
+ raise DependencyException('Version argument must be string or list.')
+ (self.is_found, not_found, found) = \
+ version_compare_many(self.modversion, self.version_reqs)
if not self.is_found:
- found_msg += [mlog.red('NO'), 'found {!r}'.format(self.modversion),
- 'but need {!r}'.format(self.version_requirement)]
+ found_msg += [mlog.red('NO'),
+ 'found {!r} but need:'.format(self.modversion),
+ ', '.join(["'{}'".format(e) for e in not_found])]
+ if found:
+ found_msg += ['; matched:',
+ ', '.join(["'{}'".format(e) for e in found])]
if not self.silent:
mlog.log(*found_msg)
if self.required:
raise DependencyException(
'Invalid version of a dependency, needed %s %s found %s.' %
- (name, self.version_requirement, self.modversion))
+ (name, not_found, self.modversion))
return
found_msg += [mlog.green('YES'), self.modversion]
if not self.silent:
@@ -301,7 +306,7 @@ class WxDependency(Dependency):
self.modversion = out.decode().strip()
version_req = kwargs.get('version', None)
if version_req is not None:
- if not mesonlib.version_compare(self.modversion, version_req):
+ if not version_compare(self.modversion, version_req, strict=True):
mlog.log('Wxwidgets version %s does not fullfill requirement %s' %\
(self.modversion, version_req))
return
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index ef99511..2167b81 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -22,58 +22,17 @@ from . import optinterpreter
from . import compilers
from .wrap import wrap
from . import mesonlib
+from mesonbuild.interpreterbase import InterpreterBase
+from mesonbuild.interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs
+from mesonbuild.interpreterbase import InterpreterException, InvalidArguments, InvalidCode
+from mesonbuild.interpreterbase import InterpreterObject, MutableInterpreterObject
import os, sys, subprocess, shutil, uuid, re
-from functools import wraps
import importlib
-import copy
run_depr_printed = False
-class InterpreterException(mesonlib.MesonException):
- pass
-
-class InvalidCode(InterpreterException):
- pass
-
-class InvalidArguments(InterpreterException):
- pass
-
-# Decorators for method calls.
-
-def check_stringlist(a, msg='Arguments must be strings.'):
- if not isinstance(a, list):
- mlog.debug('Not a list:', str(a))
- raise InvalidArguments('Argument not a list.')
- if not all(isinstance(s, str) for s in a):
- mlog.debug('Element not a string:', str(a))
- raise InvalidArguments(msg)
-
-def noPosargs(f):
- @wraps(f)
- def wrapped(self, node, args, kwargs):
- if len(args) != 0:
- raise InvalidArguments('Function does not take positional arguments.')
- return f(self, node, args, kwargs)
- return wrapped
-
-def noKwargs(f):
- @wraps(f)
- def wrapped(self, node, args, kwargs):
- if len(kwargs) != 0:
- raise InvalidArguments('Function does not take keyword arguments.')
- return f(self, node, args, kwargs)
- return wrapped
-
-def stringArgs(f):
- @wraps(f)
- def wrapped(self, node, args, kwargs):
- assert(isinstance(args, list))
- check_stringlist(args)
- return f(self, node, args, kwargs)
- return wrapped
-
def stringifyUserArguments(args):
if isinstance(args, list):
return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
@@ -83,18 +42,6 @@ def stringifyUserArguments(args):
return "'%s'" % args
raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
-class InterpreterObject():
- def __init__(self):
- self.methods = {}
-
- def method_call(self, method_name, args, kwargs):
- if method_name in self.methods:
- return self.methods[method_name](args, kwargs)
- raise InvalidCode('Unknown method "%s" in object.' % method_name)
-
-class MutableInterpreterObject(InterpreterObject):
- def __init__(self):
- super().__init__()
class TryRunResultHolder(InterpreterObject):
def __init__(self, res):
@@ -1139,16 +1086,15 @@ class MesonMain(InterpreterObject):
return args[1]
raise InterpreterException('Unknown cross property: %s.' % propname)
-class Interpreter():
+class Interpreter(InterpreterBase):
def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects'):
+ super().__init__(build.environment.get_source_dir(), subdir)
self.build = build
self.environment = build.environment
self.coredata = self.environment.get_coredata()
self.backend = backend
self.subproject = subproject
- self.subdir = subdir
- self.source_root = build.environment.get_source_dir()
self.subproject_dir = subproject_dir
option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
if os.path.exists(option_file):
@@ -1156,22 +1102,9 @@ class Interpreter():
self.build.environment.cmd_line_options.projectoptions)
oi.process(option_file)
self.build.environment.merge_options(oi.options)
- mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
- if not os.path.isfile(mesonfile):
- raise InvalidArguments('Missing Meson file in %s' % mesonfile)
- with open(mesonfile, encoding='utf8') as mf:
- code = mf.read()
- if len(code.strip()) == 0:
- raise InvalidCode('Builder file is empty.')
- assert(isinstance(code, str))
- try:
- self.ast = mparser.Parser(code).parse()
- except mesonlib.MesonException as me:
- me.file = environment.build_filename
- raise me
+ self.load_root_meson_file()
self.sanity_check_ast()
- self.variables = {}
- self.builtin = {'meson': MesonMain(build, self)}
+ self.builtin.update({'meson': MesonMain(build, self)})
self.generators = []
self.visited_subdirs = {}
self.args_frozen = False
@@ -1196,7 +1129,7 @@ class Interpreter():
self.build_def_files = [os.path.join(self.subdir, environment.build_filename)]
def build_func_dict(self):
- self.funcs = {'project' : self.func_project,
+ self.funcs.update({'project' : self.func_project,
'message' : self.func_message,
'error' : self.func_error,
'executable': self.func_executable,
@@ -1241,14 +1174,7 @@ class Interpreter():
'assert': self.func_assert,
'environment' : self.func_environment,
'join_paths' : self.func_join_paths,
- }
-
- def parse_project(self):
- """
- Parses project() and initializes languages, compilers etc. Do this
- early because we need this before we parse the rest of the AST.
- """
- self.evaluate_codeblock(self.ast, end=1)
+ })
def module_method_callback(self, invalues):
unwrap_single = False
@@ -1295,16 +1221,6 @@ class Interpreter():
def get_variables(self):
return self.variables
- def sanity_check_ast(self):
- if not isinstance(self.ast, mparser.CodeBlockNode):
- raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
- if len(self.ast.lines) == 0:
- raise InvalidCode('No statements in code.')
- first = self.ast.lines[0]
- if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
- raise InvalidCode('First statement must be a call to project')
-
-
def check_cross_stdlibs(self):
if self.build.environment.is_cross_build():
cross_info = self.build.environment.cross_info
@@ -1321,71 +1237,6 @@ class Interpreter():
except KeyError as e:
pass
- def run(self):
- # Evaluate everything after the first line, which is project() because
- # we already parsed that in self.parse_project()
- self.evaluate_codeblock(self.ast, start=1)
- mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
-
- def evaluate_codeblock(self, node, start=0, end=None):
- if node is None:
- return
- if not isinstance(node, mparser.CodeBlockNode):
- e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
- e.lineno = node.lineno
- e.colno = node.colno
- raise e
- statements = node.lines[start:end]
- i = 0
- while i < len(statements):
- cur = statements[i]
- try:
- self.evaluate_statement(cur)
- except Exception as e:
- if not(hasattr(e, 'lineno')):
- e.lineno = cur.lineno
- e.colno = cur.colno
- e.file = os.path.join(self.subdir, 'meson.build')
- raise e
- i += 1 # In THE FUTURE jump over blocks and stuff.
-
- def get_variable(self, varname):
- if varname in self.builtin:
- return self.builtin[varname]
- if varname in self.variables:
- return self.variables[varname]
- raise InvalidCode('Unknown variable "%s".' % varname)
-
- def func_set_variable(self, node, args, kwargs):
- if len(args) != 2:
- raise InvalidCode('Set_variable takes two arguments.')
- varname = args[0]
- value = self.to_native(args[1])
- self.set_variable(varname, value)
-
- @noKwargs
- def func_get_variable(self, node, args, kwargs):
- if len(args)<1 or len(args)>2:
- raise InvalidCode('Get_variable takes one or two arguments.')
- varname = args[0]
- if not isinstance(varname, str):
- raise InterpreterException('First argument must be a string.')
- try:
- return self.variables[varname]
- except KeyError:
- pass
- if len(args) == 2:
- return args[1]
- raise InterpreterException('Tried to get unknown variable "%s".' % varname)
-
- @stringArgs
- @noKwargs
- def func_is_variable(self, node, args, kwargs):
- if len(args) != 1:
- raise InvalidCode('Is_variable takes two arguments.')
- varname = args[0]
- return varname in self.variables
-
@stringArgs
@noKwargs
def func_import(self, node, args, kwargs):
@@ -1446,63 +1297,6 @@ class Interpreter():
if not value:
raise InterpreterException('Assert failed: ' + message)
- def set_variable(self, varname, variable):
- if variable is None:
- raise InvalidCode('Can not assign None to variable.')
- if not isinstance(varname, str):
- raise InvalidCode('First argument to set_variable must be a string.')
- if not self.is_assignable(variable):
- raise InvalidCode('Assigned value not of assignable type.')
- if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None:
- raise InvalidCode('Invalid variable name: ' + varname)
- if varname in self.builtin:
- raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
- self.variables[varname] = variable
-
- def evaluate_statement(self, cur):
- if isinstance(cur, mparser.FunctionNode):
- return self.function_call(cur)
- elif isinstance(cur, mparser.AssignmentNode):
- return self.assignment(cur)
- elif isinstance(cur, mparser.MethodNode):
- return self.method_call(cur)
- elif isinstance(cur, mparser.StringNode):
- return cur.value
- elif isinstance(cur, mparser.BooleanNode):
- return cur.value
- elif isinstance(cur, mparser.IfClauseNode):
- return self.evaluate_if(cur)
- elif isinstance(cur, mparser.IdNode):
- return self.get_variable(cur.value)
- elif isinstance(cur, mparser.ComparisonNode):
- return self.evaluate_comparison(cur)
- elif isinstance(cur, mparser.ArrayNode):
- return self.evaluate_arraystatement(cur)
- elif isinstance(cur, mparser.NumberNode):
- return cur.value
- elif isinstance(cur, mparser.AndNode):
- return self.evaluate_andstatement(cur)
- elif isinstance(cur, mparser.OrNode):
- return self.evaluate_orstatement(cur)
- elif isinstance(cur, mparser.NotNode):
- return self.evaluate_notstatement(cur)
- elif isinstance(cur, mparser.UMinusNode):
- return self.evaluate_uminusstatement(cur)
- elif isinstance(cur, mparser.ArithmeticNode):
- return self.evaluate_arithmeticstatement(cur)
- elif isinstance(cur, mparser.ForeachClauseNode):
- return self.evaluate_foreach(cur)
- elif isinstance(cur, mparser.PlusAssignmentNode):
- return self.evaluate_plusassign(cur)
- elif isinstance(cur, mparser.IndexNode):
- return self.evaluate_indexing(cur)
- elif isinstance(cur, mparser.TernaryNode):
- return self.evaluate_ternary(cur)
- elif self.is_elementary_type(cur):
- return cur
- else:
- raise InvalidCode("Unknown statement.")
-
def validate_arguments(self, args, argcount, arg_types):
if argcount is not None:
if argcount != len(args):
@@ -1862,7 +1656,8 @@ requirements use the version keyword argument instead.''')
if 'version' in kwargs:
wanted = kwargs['version']
found = cached_dep.get_version()
- if not cached_dep.found() or not mesonlib.version_compare(found, wanted):
+ if not cached_dep.found() or \
+ not mesonlib.version_compare_many(found, wanted)[0]:
# Cached dep has the wrong version. Check if an external
# dependency or a fallback dependency provides it.
cached_dep = None
@@ -2192,7 +1987,7 @@ requirements use the version keyword argument instead.''')
code = f.read()
assert(isinstance(code, str))
try:
- codeblock = mparser.Parser(code).parse()
+ codeblock = mparser.Parser(code, self.subdir).parse()
except mesonlib.MesonException as me:
me.file = buildfilename
raise me
@@ -2375,21 +2170,9 @@ requirements use the version keyword argument instead.''')
def func_join_paths(self, node, args, kwargs):
return os.path.join(*args).replace('\\', '/')
- def flatten(self, args):
- if isinstance(args, mparser.StringNode):
- return args.value
- if isinstance(args, (int, str, InterpreterObject)):
- return args
- result = []
- for a in args:
- if isinstance(a, list):
- rest = self.flatten(a)
- result = result + rest
- elif isinstance(a, mparser.StringNode):
- result.append(a.value)
- else:
- result.append(a)
- return result
+ def run(self):
+ super().run()
+ mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
def source_strings_to_files(self, sources):
results = []
@@ -2486,145 +2269,6 @@ requirements use the version keyword argument instead.''')
if not os.path.isfile(fname):
raise InterpreterException('Tried to add non-existing source file %s.' % s)
- def function_call(self, node):
- func_name = node.func_name
- (posargs, kwargs) = self.reduce_arguments(node.args)
- if func_name in self.funcs:
- return self.funcs[func_name](node, self.flatten(posargs), kwargs)
- else:
- raise InvalidCode('Unknown function "%s".' % func_name)
-
- def is_assignable(self, value):
- return isinstance(value, (InterpreterObject, dependencies.Dependency,
- str, int, list, mesonlib.File))
-
- def assignment(self, node):
- assert(isinstance(node, mparser.AssignmentNode))
- var_name = node.var_name
- if not isinstance(var_name, str):
- raise InvalidArguments('Tried to assign value to a non-variable.')
- value = self.evaluate_statement(node.value)
- value = self.to_native(value)
- if not self.is_assignable(value):
- raise InvalidCode('Tried to assign an invalid value to variable.')
- # For mutable objects we need to make a copy on assignment
- if isinstance(value, MutableInterpreterObject):
- value = copy.deepcopy(value)
- self.set_variable(var_name, value)
- return value
-
- def reduce_arguments(self, args):
- assert(isinstance(args, mparser.ArgumentNode))
- if args.incorrect_order():
- raise InvalidArguments('All keyword arguments must be after positional arguments.')
- reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
- reduced_kw = {}
- for key in args.kwargs.keys():
- if not isinstance(key, str):
- raise InvalidArguments('Keyword argument name is not a string.')
- a = args.kwargs[key]
- reduced_kw[key] = self.evaluate_statement(a)
- if not isinstance(reduced_pos, list):
- reduced_pos = [reduced_pos]
- return (reduced_pos, reduced_kw)
-
- def bool_method_call(self, obj, method_name, args):
- obj = self.to_native(obj)
- (posargs, _) = self.reduce_arguments(args)
- if method_name == 'to_string':
- if len(posargs) == 0:
- if obj == True:
- return 'true'
- else:
- return 'false'
- elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str):
- if obj == True:
- return posargs[0]
- else:
- return posargs[1]
- else:
- raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
- elif method_name == 'to_int':
- if obj == True:
- return 1
- else:
- return 0
- else:
- raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
-
- def int_method_call(self, obj, method_name, args):
- obj = self.to_native(obj)
- (posargs, _) = self.reduce_arguments(args)
- if method_name == 'is_even':
- if len(posargs) == 0:
- return obj % 2 == 0
- else:
- raise InterpreterException('int.is_even() must have no arguments.')
- elif method_name == 'is_odd':
- if len(posargs) == 0:
- return obj % 2 != 0
- else:
- raise InterpreterException('int.is_odd() must have no arguments.')
- else:
- raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
-
- def string_method_call(self, obj, method_name, args):
- obj = self.to_native(obj)
- (posargs, _) = self.reduce_arguments(args)
- if method_name == 'strip':
- return obj.strip()
- elif method_name == 'format':
- return self.format_string(obj, args)
- elif method_name == 'to_upper':
- return obj.upper()
- elif method_name == 'to_lower':
- return obj.lower()
- elif method_name == 'underscorify':
- return re.sub(r'[^a-zA-Z0-9]', '_', obj)
- elif method_name == 'split':
- if len(posargs) > 1:
- raise InterpreterException('Split() must have at most one argument.')
- elif len(posargs) == 1:
- s = posargs[0]
- if not isinstance(s, str):
- raise InterpreterException('Split() argument must be a string')
- return obj.split(s)
- else:
- return obj.split()
- elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith':
- s = posargs[0]
- if not isinstance(s, str):
- raise InterpreterException('Argument must be a string.')
- if method_name == 'startswith':
- return obj.startswith(s)
- elif method_name == 'contains':
- return obj.find(s) >= 0
- return obj.endswith(s)
- elif method_name == 'to_int':
- try:
- return int(obj)
- except Exception:
- raise InterpreterException('String {!r} cannot be converted to int'.format(obj))
- elif method_name == 'join':
- if len(posargs) != 1:
- raise InterpreterException('Join() takes exactly one argument.')
- strlist = posargs[0]
- check_stringlist(strlist)
- return obj.join(strlist)
- elif method_name == 'version_compare':
- if len(posargs) != 1:
- raise InterpreterException('Version_compare() takes exactly one argument.')
- cmpr = posargs[0]
- if not isinstance(cmpr, str):
- raise InterpreterException('Version_compare() argument must be a string.')
- return mesonlib.version_compare(obj, cmpr)
- raise InterpreterException('Unknown method "%s" for a string.' % method_name)
-
- def to_native(self, arg):
- if isinstance(arg, (mparser.StringNode, mparser.NumberNode,
- mparser.BooleanNode)):
- return arg.value
- return arg
def format_string(self, templ, args):
templ = self.to_native(templ)
@@ -2637,32 +2281,6 @@ requirements use the version keyword argument instead.''')
templ = templ.replace('@{}@'.format(i), str(arg))
return templ
- def method_call(self, node):
- invokable = node.source_object
- if isinstance(invokable, mparser.IdNode):
- object_name = invokable.value
- obj = self.get_variable(object_name)
- else:
- obj = self.evaluate_statement(invokable)
- method_name = node.name
- args = node.args
- if isinstance(obj, mparser.StringNode):
- obj = obj.get_value()
- if isinstance(obj, str):
- return self.string_method_call(obj, method_name, args)
- if isinstance(obj, bool):
- return self.bool_method_call(obj, method_name, args)
- if isinstance(obj, int):
- return self.int_method_call(obj, method_name, args)
- if isinstance(obj, list):
- return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0])
- if not isinstance(obj, InterpreterObject):
- raise InvalidArguments('Variable "%s" is not callable.' % object_name)
- (args, kwargs) = self.reduce_arguments(args)
- if method_name == 'extract_objects':
- self.validate_extraction(obj.held_object)
- return obj.method_call(method_name, self.flatten(args), kwargs)
-
# Only permit object extraction from the same subproject
def validate_extraction(self, buildtarget):
if not self.subdir.startswith(self.subproject_dir):
@@ -2674,20 +2292,6 @@ requirements use the version keyword argument instead.''')
if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]:
raise InterpreterException('Tried to extract objects from a different subproject.')
- def array_method_call(self, obj, method_name, args):
- if method_name == 'contains':
- return self.check_contains(obj, args)
- elif method_name == 'length':
- return len(obj)
- elif method_name == 'get':
- index = args[0]
- if not isinstance(index, int):
- raise InvalidArguments('Array index must be a number.')
- if index < -len(obj) or index >= len(obj):
- raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj)))
- return obj[index]
- raise InterpreterException('Arrays do not have a method called "%s".' % method_name)
-
def check_contains(self, obj, args):
if len(args) != 1:
raise InterpreterException('Contains method takes exactly one argument.')
@@ -2704,183 +2308,5 @@ requirements use the version keyword argument instead.''')
pass
return False
- def evaluate_if(self, node):
- assert(isinstance(node, mparser.IfClauseNode))
- for i in node.ifs:
- result = self.evaluate_statement(i.condition)
- if not(isinstance(result, bool)):
- raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
- if result:
- self.evaluate_codeblock(i.block)
- return
- if not isinstance(node.elseblock, mparser.EmptyNode):
- self.evaluate_codeblock(node.elseblock)
-
- def evaluate_ternary(self, node):
- assert(isinstance(node, mparser.TernaryNode))
- result = self.evaluate_statement(node.condition)
- if not isinstance(result, bool):
- raise InterpreterException('Ternary condition is not boolean.')
- if result:
- return self.evaluate_statement(node.trueblock)
- else:
- return self.evaluate_statement(node.falseblock)
-
- def evaluate_foreach(self, node):
- assert(isinstance(node, mparser.ForeachClauseNode))
- varname = node.varname.value
- items = self.evaluate_statement(node.items)
- if not isinstance(items, list):
- raise InvalidArguments('Items of foreach loop is not an array')
- for item in items:
- self.set_variable(varname, item)
- self.evaluate_codeblock(node.block)
-
- def evaluate_plusassign(self, node):
- assert(isinstance(node, mparser.PlusAssignmentNode))
- varname = node.var_name
- addition = self.evaluate_statement(node.value)
- # Remember that all variables are immutable. We must always create a
- # full new variable and then assign it.
- old_variable = self.get_variable(varname)
- if isinstance(old_variable, str):
- if not isinstance(addition, str):
- raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string')
- new_value = old_variable + addition
- elif isinstance(old_variable, int):
- if not isinstance(addition, int):
- raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int')
- new_value = old_variable + addition
- elif not isinstance(old_variable, list):
- raise InvalidArguments('The += operator currently only works with arrays, strings or ints ')
- # Add other data types here.
- else:
- if isinstance(addition, list):
- new_value = old_variable + addition
- else:
- new_value = old_variable + [addition]
- self.set_variable(varname, new_value)
-
- def evaluate_indexing(self, node):
- assert(isinstance(node, mparser.IndexNode))
- iobject = self.evaluate_statement(node.iobject)
- if not isinstance(iobject, list):
- raise InterpreterException('Tried to index a non-array object.')
- index = self.evaluate_statement(node.index)
- if not isinstance(index, int):
- raise InterpreterException('Index value is not an integer.')
- if index < -len(iobject) or index >= len(iobject):
- raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject)))
- return iobject[index]
-
- def is_elementary_type(self, v):
- return isinstance(v, (int, float, str, bool, list))
-
- def evaluate_comparison(self, node):
- v1 = self.evaluate_statement(node.left)
- v2 = self.evaluate_statement(node.right)
- if self.is_elementary_type(v1):
- val1 = v1
- else:
- val1 = v1.value
- if self.is_elementary_type(v2):
- val2 = v2
- else:
- val2 = v2.value
- if node.ctype == '==':
- return val1 == val2
- elif node.ctype == '!=':
- return val1 != val2
- elif node.ctype == '<':
- return val1 < val2
- elif node.ctype == '<=':
- return val1 <= val2
- elif node.ctype == '>':
- return val1 > val2
- elif node.ctype == '>=':
- return val1 >= val2
- else:
- raise InvalidCode('You broke my compare eval.')
-
- def evaluate_andstatement(self, cur):
- l = self.evaluate_statement(cur.left)
- if isinstance(l, mparser.BooleanNode):
- l = l.value
- if not isinstance(l, bool):
- raise InterpreterException('First argument to "and" is not a boolean.')
- if not l:
- return False
- r = self.evaluate_statement(cur.right)
- if isinstance(r, mparser.BooleanNode):
- r = r.value
- if not isinstance(r, bool):
- raise InterpreterException('Second argument to "and" is not a boolean.')
- return r
-
- def evaluate_orstatement(self, cur):
- l = self.evaluate_statement(cur.left)
- if isinstance(l, mparser.BooleanNode):
- l = l.get_value()
- if not isinstance(l, bool):
- raise InterpreterException('First argument to "or" is not a boolean.')
- if l:
- return True
- r = self.evaluate_statement(cur.right)
- if isinstance(r, mparser.BooleanNode):
- r = r.get_value()
- if not isinstance(r, bool):
- raise InterpreterException('Second argument to "or" is not a boolean.')
- return r
-
- def evaluate_notstatement(self, cur):
- v = self.evaluate_statement(cur.value)
- if isinstance(v, mparser.BooleanNode):
- v = v.value
- if not isinstance(v, bool):
- raise InterpreterException('Argument to "not" is not a boolean.')
- return not v
-
- def evaluate_uminusstatement(self, cur):
- v = self.evaluate_statement(cur.value)
- if isinstance(v, mparser.NumberNode):
- v = v.value
- if not isinstance(v, int):
- raise InterpreterException('Argument to negation is not an integer.')
- return -v
-
- def evaluate_arithmeticstatement(self, cur):
- l = self.to_native(self.evaluate_statement(cur.left))
- r = self.to_native(self.evaluate_statement(cur.right))
-
- if cur.operation == 'add':
- try:
- return l + r
- except Exception as e:
- raise InvalidCode('Invalid use of addition: ' + str(e))
- elif cur.operation == 'sub':
- if not isinstance(l, int) or not isinstance(r, int):
- raise InvalidCode('Subtraction works only with integers.')
- return l - r
- elif cur.operation == 'mul':
- if not isinstance(l, int) or not isinstance(r, int):
- raise InvalidCode('Multiplication works only with integers.')
- return l * r
- elif cur.operation == 'div':
- if not isinstance(l, int) or not isinstance(r, int):
- raise InvalidCode('Division works only with integers.')
- return l // r
- elif cur.operation == 'mod':
- if not isinstance(l, int) or not isinstance(r, int):
- raise InvalidCode('Modulo works only with integers.')
- return l % r
- else:
- raise InvalidCode('You broke me.')
-
- def evaluate_arraystatement(self, cur):
- (arguments, kwargs) = self.reduce_arguments(cur.args)
- if len(kwargs) > 0:
- raise InvalidCode('Keyword arguments are invalid in array construction.')
- return arguments
-
def is_subproject(self):
return self.subproject != ''
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
new file mode 100644
index 0000000..97814f4
--- /dev/null
+++ b/mesonbuild/interpreterbase.py
@@ -0,0 +1,636 @@
+# Copyright 2016 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 . import mparser, mesonlib, mlog
+from . import environment, dependencies
+
+import os, copy, re
+from functools import wraps
+
+# Decorators for method calls.
+
+def check_stringlist(a, msg='Arguments must be strings.'):
+ if not isinstance(a, list):
+ mlog.debug('Not a list:', str(a))
+ raise InvalidArguments('Argument not a list.')
+ if not all(isinstance(s, str) for s in a):
+ mlog.debug('Element not a string:', str(a))
+ raise InvalidArguments(msg)
+
+def noPosargs(f):
+ @wraps(f)
+ def wrapped(self, node, args, kwargs):
+ if len(args) != 0:
+ raise InvalidArguments('Function does not take positional arguments.')
+ return f(self, node, args, kwargs)
+ return wrapped
+
+def noKwargs(f):
+ @wraps(f)
+ def wrapped(self, node, args, kwargs):
+ if len(kwargs) != 0:
+ raise InvalidArguments('Function does not take keyword arguments.')
+ return f(self, node, args, kwargs)
+ return wrapped
+
+def stringArgs(f):
+ @wraps(f)
+ def wrapped(self, node, args, kwargs):
+ assert(isinstance(args, list))
+ check_stringlist(args)
+ return f(self, node, args, kwargs)
+ return wrapped
+
+
+class InterpreterException(mesonlib.MesonException):
+ pass
+
+class InvalidCode(InterpreterException):
+ pass
+
+class InvalidArguments(InterpreterException):
+ pass
+
+class InterpreterObject():
+ def __init__(self):
+ self.methods = {}
+
+ def method_call(self, method_name, args, kwargs):
+ if method_name in self.methods:
+ return self.methods[method_name](args, kwargs)
+ raise InvalidCode('Unknown method "%s" in object.' % method_name)
+
+class MutableInterpreterObject(InterpreterObject):
+ def __init__(self):
+ super().__init__()
+
+
+class InterpreterBase:
+ def __init__(self, source_root, subdir):
+ self.source_root = source_root
+ self.funcs = {}
+ self.builtin = {}
+ self.subdir = subdir
+ self.variables = {}
+
+ def load_root_meson_file(self):
+ mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
+ if not os.path.isfile(mesonfile):
+ raise InvalidArguments('Missing Meson file in %s' % mesonfile)
+ with open(mesonfile, encoding='utf8') as mf:
+ code = mf.read()
+ if len(code.strip()) == 0:
+ raise InvalidCode('Builder file is empty.')
+ assert(isinstance(code, str))
+ try:
+ self.ast = mparser.Parser(code, self.subdir).parse()
+ except mesonlib.MesonException as me:
+ me.file = environment.build_filename
+ raise me
+
+ def parse_project(self):
+ """
+ Parses project() and initializes languages, compilers etc. Do this
+ early because we need this before we parse the rest of the AST.
+ """
+ self.evaluate_codeblock(self.ast, end=1)
+
+ def sanity_check_ast(self):
+ if not isinstance(self.ast, mparser.CodeBlockNode):
+ raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
+ if len(self.ast.lines) == 0:
+ raise InvalidCode('No statements in code.')
+ first = self.ast.lines[0]
+ if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
+ raise InvalidCode('First statement must be a call to project')
+
+ def run(self):
+ # Evaluate everything after the first line, which is project() because
+ # we already parsed that in self.parse_project()
+ self.evaluate_codeblock(self.ast, start=1)
+
+ def evaluate_codeblock(self, node, start=0, end=None):
+ if node is None:
+ return
+ if not isinstance(node, mparser.CodeBlockNode):
+ e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.')
+ e.lineno = node.lineno
+ e.colno = node.colno
+ raise e
+ statements = node.lines[start:end]
+ i = 0
+ while i < len(statements):
+ cur = statements[i]
+ try:
+ self.evaluate_statement(cur)
+ except Exception as e:
+ if not(hasattr(e, 'lineno')):
+ e.lineno = cur.lineno
+ e.colno = cur.colno
+ e.file = os.path.join(self.subdir, 'meson.build')
+ raise e
+ i += 1 # In THE FUTURE jump over blocks and stuff.
+
+ def evaluate_statement(self, cur):
+ if isinstance(cur, mparser.FunctionNode):
+ return self.function_call(cur)
+ elif isinstance(cur, mparser.AssignmentNode):
+ return self.assignment(cur)
+ elif isinstance(cur, mparser.MethodNode):
+ return self.method_call(cur)
+ elif isinstance(cur, mparser.StringNode):
+ return cur.value
+ elif isinstance(cur, mparser.BooleanNode):
+ return cur.value
+ elif isinstance(cur, mparser.IfClauseNode):
+ return self.evaluate_if(cur)
+ elif isinstance(cur, mparser.IdNode):
+ return self.get_variable(cur.value)
+ elif isinstance(cur, mparser.ComparisonNode):
+ return self.evaluate_comparison(cur)
+ elif isinstance(cur, mparser.ArrayNode):
+ return self.evaluate_arraystatement(cur)
+ elif isinstance(cur, mparser.NumberNode):
+ return cur.value
+ elif isinstance(cur, mparser.AndNode):
+ return self.evaluate_andstatement(cur)
+ elif isinstance(cur, mparser.OrNode):
+ return self.evaluate_orstatement(cur)
+ elif isinstance(cur, mparser.NotNode):
+ return self.evaluate_notstatement(cur)
+ elif isinstance(cur, mparser.UMinusNode):
+ return self.evaluate_uminusstatement(cur)
+ elif isinstance(cur, mparser.ArithmeticNode):
+ return self.evaluate_arithmeticstatement(cur)
+ elif isinstance(cur, mparser.ForeachClauseNode):
+ return self.evaluate_foreach(cur)
+ elif isinstance(cur, mparser.PlusAssignmentNode):
+ return self.evaluate_plusassign(cur)
+ elif isinstance(cur, mparser.IndexNode):
+ return self.evaluate_indexing(cur)
+ elif isinstance(cur, mparser.TernaryNode):
+ return self.evaluate_ternary(cur)
+ elif self.is_elementary_type(cur):
+ return cur
+ else:
+ raise InvalidCode("Unknown statement.")
+
+ def evaluate_arraystatement(self, cur):
+ (arguments, kwargs) = self.reduce_arguments(cur.args)
+ if len(kwargs) > 0:
+ raise InvalidCode('Keyword arguments are invalid in array construction.')
+ return arguments
+
+ def evaluate_notstatement(self, cur):
+ v = self.evaluate_statement(cur.value)
+ if isinstance(v, mparser.BooleanNode):
+ v = v.value
+ if not isinstance(v, bool):
+ raise InterpreterException('Argument to "not" is not a boolean.')
+ return not v
+
+ def evaluate_if(self, node):
+ assert(isinstance(node, mparser.IfClauseNode))
+ for i in node.ifs:
+ result = self.evaluate_statement(i.condition)
+ if not(isinstance(result, bool)):
+ raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
+ if result:
+ self.evaluate_codeblock(i.block)
+ return
+ if not isinstance(node.elseblock, mparser.EmptyNode):
+ self.evaluate_codeblock(node.elseblock)
+
+ def evaluate_comparison(self, node):
+ v1 = self.evaluate_statement(node.left)
+ v2 = self.evaluate_statement(node.right)
+ if self.is_elementary_type(v1):
+ val1 = v1
+ else:
+ val1 = v1.value
+ if self.is_elementary_type(v2):
+ val2 = v2
+ else:
+ val2 = v2.value
+ if node.ctype == '==':
+ return val1 == val2
+ elif node.ctype == '!=':
+ return val1 != val2
+ elif node.ctype == '<':
+ return val1 < val2
+ elif node.ctype == '<=':
+ return val1 <= val2
+ elif node.ctype == '>':
+ return val1 > val2
+ elif node.ctype == '>=':
+ return val1 >= val2
+ else:
+ raise InvalidCode('You broke my compare eval.')
+
+ def evaluate_andstatement(self, cur):
+ l = self.evaluate_statement(cur.left)
+ if isinstance(l, mparser.BooleanNode):
+ l = l.value
+ if not isinstance(l, bool):
+ raise InterpreterException('First argument to "and" is not a boolean.')
+ if not l:
+ return False
+ r = self.evaluate_statement(cur.right)
+ if isinstance(r, mparser.BooleanNode):
+ r = r.value
+ if not isinstance(r, bool):
+ raise InterpreterException('Second argument to "and" is not a boolean.')
+ return r
+
+ def evaluate_orstatement(self, cur):
+ l = self.evaluate_statement(cur.left)
+ if isinstance(l, mparser.BooleanNode):
+ l = l.get_value()
+ if not isinstance(l, bool):
+ raise InterpreterException('First argument to "or" is not a boolean.')
+ if l:
+ return True
+ r = self.evaluate_statement(cur.right)
+ if isinstance(r, mparser.BooleanNode):
+ r = r.get_value()
+ if not isinstance(r, bool):
+ raise InterpreterException('Second argument to "or" is not a boolean.')
+ return r
+
+ def evaluate_uminusstatement(self, cur):
+ v = self.evaluate_statement(cur.value)
+ if isinstance(v, mparser.NumberNode):
+ v = v.value
+ if not isinstance(v, int):
+ raise InterpreterException('Argument to negation is not an integer.')
+ return -v
+
+ def evaluate_arithmeticstatement(self, cur):
+ l = self.to_native(self.evaluate_statement(cur.left))
+ r = self.to_native(self.evaluate_statement(cur.right))
+
+ if cur.operation == 'add':
+ try:
+ return l + r
+ except Exception as e:
+ raise InvalidCode('Invalid use of addition: ' + str(e))
+ elif cur.operation == 'sub':
+ if not isinstance(l, int) or not isinstance(r, int):
+ raise InvalidCode('Subtraction works only with integers.')
+ return l - r
+ elif cur.operation == 'mul':
+ if not isinstance(l, int) or not isinstance(r, int):
+ raise InvalidCode('Multiplication works only with integers.')
+ return l * r
+ elif cur.operation == 'div':
+ if not isinstance(l, int) or not isinstance(r, int):
+ raise InvalidCode('Division works only with integers.')
+ return l // r
+ elif cur.operation == 'mod':
+ if not isinstance(l, int) or not isinstance(r, int):
+ raise InvalidCode('Modulo works only with integers.')
+ return l % r
+ else:
+ raise InvalidCode('You broke me.')
+
+ def evaluate_ternary(self, node):
+ assert(isinstance(node, mparser.TernaryNode))
+ result = self.evaluate_statement(node.condition)
+ if not isinstance(result, bool):
+ raise InterpreterException('Ternary condition is not boolean.')
+ if result:
+ return self.evaluate_statement(node.trueblock)
+ else:
+ return self.evaluate_statement(node.falseblock)
+
+ def evaluate_foreach(self, node):
+ assert(isinstance(node, mparser.ForeachClauseNode))
+ varname = node.varname.value
+ items = self.evaluate_statement(node.items)
+ if not isinstance(items, list):
+ raise InvalidArguments('Items of foreach loop is not an array')
+ for item in items:
+ self.set_variable(varname, item)
+ self.evaluate_codeblock(node.block)
+
+ def evaluate_plusassign(self, node):
+ assert(isinstance(node, mparser.PlusAssignmentNode))
+ varname = node.var_name
+ addition = self.evaluate_statement(node.value)
+ # Remember that all variables are immutable. We must always create a
+ # full new variable and then assign it.
+ old_variable = self.get_variable(varname)
+ if isinstance(old_variable, str):
+ if not isinstance(addition, str):
+ raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string')
+ new_value = old_variable + addition
+ elif isinstance(old_variable, int):
+ if not isinstance(addition, int):
+ raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int')
+ new_value = old_variable + addition
+ elif not isinstance(old_variable, list):
+ raise InvalidArguments('The += operator currently only works with arrays, strings or ints ')
+ # Add other data types here.
+ else:
+ if isinstance(addition, list):
+ new_value = old_variable + addition
+ else:
+ new_value = old_variable + [addition]
+ self.set_variable(varname, new_value)
+
+ def evaluate_indexing(self, node):
+ assert(isinstance(node, mparser.IndexNode))
+ iobject = self.evaluate_statement(node.iobject)
+ if not isinstance(iobject, list):
+ raise InterpreterException('Tried to index a non-array object.')
+ index = self.evaluate_statement(node.index)
+ if not isinstance(index, int):
+ raise InterpreterException('Index value is not an integer.')
+ if index < -len(iobject) or index >= len(iobject):
+ raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject)))
+ return iobject[index]
+
+ def function_call(self, node):
+ func_name = node.func_name
+ (posargs, kwargs) = self.reduce_arguments(node.args)
+ if func_name in self.funcs:
+ return self.funcs[func_name](node, self.flatten(posargs), kwargs)
+ else:
+ self.unknown_function_called(func_name)
+
+ def method_call(self, node):
+ invokable = node.source_object
+ if isinstance(invokable, mparser.IdNode):
+ object_name = invokable.value
+ obj = self.get_variable(object_name)
+ else:
+ obj = self.evaluate_statement(invokable)
+ method_name = node.name
+ args = node.args
+ if isinstance(obj, mparser.StringNode):
+ obj = obj.get_value()
+ if isinstance(obj, str):
+ return self.string_method_call(obj, method_name, args)
+ if isinstance(obj, bool):
+ return self.bool_method_call(obj, method_name, args)
+ if isinstance(obj, int):
+ return self.int_method_call(obj, method_name, args)
+ if isinstance(obj, list):
+ return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0])
+ if not isinstance(obj, InterpreterObject):
+ raise InvalidArguments('Variable "%s" is not callable.' % object_name)
+ (args, kwargs) = self.reduce_arguments(args)
+ if method_name == 'extract_objects':
+ self.validate_extraction(obj.held_object)
+ return obj.method_call(method_name, self.flatten(args), kwargs)
+
+ def bool_method_call(self, obj, method_name, args):
+ obj = self.to_native(obj)
+ (posargs, _) = self.reduce_arguments(args)
+ if method_name == 'to_string':
+ if len(posargs) == 0:
+ if obj == True:
+ return 'true'
+ else:
+ return 'false'
+ elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str):
+ if obj == True:
+ return posargs[0]
+ else:
+ return posargs[1]
+ else:
+ raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.')
+ elif method_name == 'to_int':
+ if obj == True:
+ return 1
+ else:
+ return 0
+ else:
+ raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
+
+ def int_method_call(self, obj, method_name, args):
+ obj = self.to_native(obj)
+ (posargs, _) = self.reduce_arguments(args)
+ if method_name == 'is_even':
+ if len(posargs) == 0:
+ return obj % 2 == 0
+ else:
+ raise InterpreterException('int.is_even() must have no arguments.')
+ elif method_name == 'is_odd':
+ if len(posargs) == 0:
+ return obj % 2 != 0
+ else:
+ raise InterpreterException('int.is_odd() must have no arguments.')
+ else:
+ raise InterpreterException('Unknown method "%s" for an integer.' % method_name)
+
+ def string_method_call(self, obj, method_name, args):
+ obj = self.to_native(obj)
+ (posargs, _) = self.reduce_arguments(args)
+ if method_name == 'strip':
+ return obj.strip()
+ elif method_name == 'format':
+ return self.format_string(obj, args)
+ elif method_name == 'to_upper':
+ return obj.upper()
+ elif method_name == 'to_lower':
+ return obj.lower()
+ elif method_name == 'underscorify':
+ return re.sub(r'[^a-zA-Z0-9]', '_', obj)
+ elif method_name == 'split':
+ if len(posargs) > 1:
+ raise InterpreterException('Split() must have at most one argument.')
+ elif len(posargs) == 1:
+ s = posargs[0]
+ if not isinstance(s, str):
+ raise InterpreterException('Split() argument must be a string')
+ return obj.split(s)
+ else:
+ return obj.split()
+ elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith':
+ s = posargs[0]
+ if not isinstance(s, str):
+ raise InterpreterException('Argument must be a string.')
+ if method_name == 'startswith':
+ return obj.startswith(s)
+ elif method_name == 'contains':
+ return obj.find(s) >= 0
+ return obj.endswith(s)
+ elif method_name == 'to_int':
+ try:
+ return int(obj)
+ except Exception:
+ raise InterpreterException('String {!r} cannot be converted to int'.format(obj))
+ elif method_name == 'join':
+ if len(posargs) != 1:
+ raise InterpreterException('Join() takes exactly one argument.')
+ strlist = posargs[0]
+ check_stringlist(strlist)
+ return obj.join(strlist)
+ elif method_name == 'version_compare':
+ if len(posargs) != 1:
+ raise InterpreterException('Version_compare() takes exactly one argument.')
+ cmpr = posargs[0]
+ if not isinstance(cmpr, str):
+ raise InterpreterException('Version_compare() argument must be a string.')
+ return mesonlib.version_compare(obj, cmpr)
+ raise InterpreterException('Unknown method "%s" for a string.' % method_name)
+
+ def unknown_function_called(self, func_name):
+ raise InvalidCode('Unknown function "%s".' % func_name)
+
+ def array_method_call(self, obj, method_name, args):
+ if method_name == 'contains':
+ return self.check_contains(obj, args)
+ elif method_name == 'length':
+ return len(obj)
+ elif method_name == 'get':
+ index = args[0]
+ if not isinstance(index, int):
+ raise InvalidArguments('Array index must be a number.')
+ if index < -len(obj) or index >= len(obj):
+ raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj)))
+ return obj[index]
+ raise InterpreterException('Arrays do not have a method called "%s".' % method_name)
+
+
+ def reduce_arguments(self, args):
+ assert(isinstance(args, mparser.ArgumentNode))
+ if args.incorrect_order():
+ raise InvalidArguments('All keyword arguments must be after positional arguments.')
+ reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
+ reduced_kw = {}
+ for key in args.kwargs.keys():
+ if not isinstance(key, str):
+ raise InvalidArguments('Keyword argument name is not a string.')
+ a = args.kwargs[key]
+ reduced_kw[key] = self.evaluate_statement(a)
+ if not isinstance(reduced_pos, list):
+ reduced_pos = [reduced_pos]
+ return (reduced_pos, reduced_kw)
+
+ def flatten(self, args):
+ if isinstance(args, mparser.StringNode):
+ return args.value
+ if isinstance(args, (int, str, InterpreterObject)):
+ return args
+ result = []
+ for a in args:
+ if isinstance(a, list):
+ rest = self.flatten(a)
+ result = result + rest
+ elif isinstance(a, mparser.StringNode):
+ result.append(a.value)
+ else:
+ result.append(a)
+ return result
+
+ def assignment(self, node):
+ assert(isinstance(node, mparser.AssignmentNode))
+ var_name = node.var_name
+ if not isinstance(var_name, str):
+ raise InvalidArguments('Tried to assign value to a non-variable.')
+ value = self.evaluate_statement(node.value)
+ value = self.to_native(value)
+ if not self.is_assignable(value):
+ raise InvalidCode('Tried to assign an invalid value to variable.')
+ # For mutable objects we need to make a copy on assignment
+ if isinstance(value, MutableInterpreterObject):
+ value = copy.deepcopy(value)
+ self.set_variable(var_name, value)
+ return value
+
+ def set_variable(self, varname, variable):
+ if variable is None:
+ raise InvalidCode('Can not assign None to variable.')
+ if not isinstance(varname, str):
+ raise InvalidCode('First argument to set_variable must be a string.')
+ if not self.is_assignable(variable):
+ raise InvalidCode('Assigned value not of assignable type.')
+ if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None:
+ raise InvalidCode('Invalid variable name: ' + varname)
+ if varname in self.builtin:
+ raise InvalidCode('Tried to overwrite internal variable "%s"' % varname)
+ self.variables[varname] = variable
+
+ def get_variable(self, varname):
+ if varname in self.builtin:
+ return self.builtin[varname]
+ if varname in self.variables:
+ return self.variables[varname]
+ raise InvalidCode('Unknown variable "%s".' % varname)
+
+ def to_native(self, arg):
+ if isinstance(arg, (mparser.StringNode, mparser.NumberNode,
+ mparser.BooleanNode)):
+ return arg.value
+ return arg
+
+ def is_assignable(self, value):
+ return isinstance(value, (InterpreterObject, dependencies.Dependency,
+ str, int, list, mesonlib.File))
+
+ def func_build_target(self, node, args, kwargs):
+ if 'target_type' not in kwargs:
+ raise InterpreterException('Missing target_type keyword argument')
+ target_type = kwargs.pop('target_type')
+ if target_type == 'executable':
+ return self.func_executable(node, args, kwargs)
+ elif target_type == 'shared_library':
+ return self.func_shared_lib(node, args, kwargs)
+ elif target_type == 'static_library':
+ return self.func_static_lib(node, args, kwargs)
+ elif target_type == 'library':
+ return self.func_library(node, args, kwargs)
+ elif target_type == 'jar':
+ return self.func_jar(node, args, kwargs)
+ else:
+ raise InterpreterException('Unknown target_type.')
+
+ def func_set_variable(self, node, args, kwargs):
+ if len(args) != 2:
+ raise InvalidCode('Set_variable takes two arguments.')
+ varname = args[0]
+ value = self.to_native(args[1])
+ self.set_variable(varname, value)
+
+# @noKwargs
+ def func_get_variable(self, node, args, kwargs):
+ if len(args)<1 or len(args)>2:
+ raise InvalidCode('Get_variable takes one or two arguments.')
+ varname = args[0]
+ if not isinstance(varname, str):
+ raise InterpreterException('First argument must be a string.')
+ try:
+ return self.variables[varname]
+ except KeyError:
+ pass
+ if len(args) == 2:
+ return args[1]
+ raise InterpreterException('Tried to get unknown variable "%s".' % varname)
+
+ @stringArgs
+ @noKwargs
+ def func_is_variable(self, node, args, kwargs):
+ if len(args) != 1:
+ raise InvalidCode('Is_variable takes two arguments.')
+ varname = args[0]
+ return varname in self.variables
+
+ def is_elementary_type(self, v):
+ return isinstance(v, (int, float, str, bool, list))
+
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index 4d9cc69..4670685 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -146,21 +146,26 @@ def detect_vcs(source_dir):
return vcs
return None
-def grab_leading_numbers(vstr):
+def grab_leading_numbers(vstr, strict=False):
result = []
for x in vstr.split('.'):
try:
result.append(int(x))
- except ValueError:
+ except ValueError as e:
+ if strict:
+ msg = 'Invalid version to compare against: {!r}; only ' \
+ 'numeric digits separated by "." are allowed: ' + str(e)
+ raise MesonException(msg.format(vstr))
break
return result
numpart = re.compile('[0-9.]+')
-def version_compare(vstr1, vstr2):
+def version_compare(vstr1, vstr2, strict=False):
match = numpart.match(vstr1.strip())
if match is None:
- raise MesonException('Uncomparable version string %s.' % vstr1)
+ msg = 'Uncomparable version string {!r}.'
+ raise MesonException(msg.format(vstr1))
vstr1 = match.group(0)
if vstr2.startswith('>='):
cmpop = operator.ge
@@ -185,10 +190,22 @@ def version_compare(vstr1, vstr2):
vstr2 = vstr2[1:]
else:
cmpop = operator.eq
- varr1 = grab_leading_numbers(vstr1)
- varr2 = grab_leading_numbers(vstr2)
+ varr1 = grab_leading_numbers(vstr1, strict)
+ varr2 = grab_leading_numbers(vstr2, strict)
return cmpop(varr1, varr2)
+def version_compare_many(vstr1, conditions):
+ if not isinstance(conditions, (list, tuple)):
+ conditions = [conditions]
+ found = []
+ not_found = []
+ for req in conditions:
+ if not version_compare(vstr1, req, strict=True):
+ not_found.append(req)
+ else:
+ found.append(req)
+ return (not_found == [], not_found, found)
+
def default_libdir():
if is_debianlike():
try:
diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py
index 1d4863c..71d42e3 100644
--- a/mesonbuild/mesonmain.py
+++ b/mesonbuild/mesonmain.py
@@ -115,7 +115,9 @@ class MesonApp():
msg = '''Trying to run Meson on a build directory that has already been configured.
If you want to build it, just run your build command (e.g. ninja) inside the
build directory. Meson will autodetect any changes in your setup and regenerate
-itself as required.'''
+itself as required.
+
+If you want to change option values, use the mesonconf tool instead.'''
raise RuntimeError(msg)
else:
if handshake:
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index 492bf3f..c1af818 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -168,7 +168,10 @@ def list_tests(testdata):
else:
fname = t.fname
to['cmd'] = fname + t.cmd_args
- to['env'] = t.env
+ if isinstance(t.env, build.EnvironmentVariables):
+ to['env'] = t.env.get_env(os.environ)
+ else:
+ to['env'] = t.env
to['name'] = t.name
to['workdir'] = t.workdir
to['timeout'] = t.timeout
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 7c4fb1d..241a531 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -36,6 +36,27 @@ gresource_dep_needed_version = '>= 2.52.0'
native_glib_version = None
girwarning_printed = False
gresource_warning_printed = False
+_gir_has_extra_lib_arg = None
+
+def gir_has_extra_lib_arg():
+ global _gir_has_extra_lib_arg
+ if _gir_has_extra_lib_arg is not None:
+ return _gir_has_extra_lib_arg
+
+ _gir_has_extra_lib_arg = False
+ try:
+ scanner_options = subprocess.check_output(['g-ir-scanner', '--help']).decode()
+ _gir_has_extra_lib_arg = '--extra-library' in scanner_options
+ except (FileNotFound, subprocess.CalledProcessError):
+ pass
+ return _gir_has_extra_lib_arg
+
+def find_program(program_name, target_name):
+ program = dependencies.ExternalProgram(program_name)
+ if not program.found():
+ raise MesonException('%s can\'t be generated as %s could not be found' % (
+ target_name, program_name))
+ return program
class GnomeModule:
@@ -104,12 +125,33 @@ can not be used with the current version of glib-compiled-resources, due to
if 'c_name' in kwargs:
cmd += ['--c-name', kwargs.pop('c_name')]
+ export = kwargs.pop('export', False)
+ if not export:
+ cmd += ['--internal']
+
cmd += ['--generate', '--target', '@OUTPUT@']
cmd += mesonlib.stringlistify(kwargs.pop('extra_args', []))
+ gresource = kwargs.pop('gresource_bundle', False)
+ if gresource:
+ output = args[0] + '.gresource'
+ name = args[0] + '_gresource'
+ else:
+ output = args[0] + '.c'
+ name = args[0] + '_c'
+
+ if kwargs.get('install', False) and not gresource:
+ raise MesonException('The install kwarg only applies to gresource bundles, see install_header')
+
+ install_header = kwargs.pop('install_header', False)
+ if install_header and gresource:
+ raise MesonException('The install_header kwarg does not apply to gresource bundles')
+ if install_header and not export:
+ raise MesonException('GResource header is installed yet export is not enabled')
+
kwargs['input'] = args[1]
- kwargs['output'] = args[0] + '.c'
+ kwargs['output'] = output
kwargs['depends'] = depends
if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
# This will eventually go out of sync if dependencies are added
@@ -119,7 +161,10 @@ can not be used with the current version of glib-compiled-resources, due to
depfile = kwargs['output'] + '.d'
kwargs['depfile'] = depfile
kwargs['command'] = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@']
- target_c = build.CustomTarget(args[0] + '_c', state.subdir, kwargs)
+ target_c = build.CustomTarget(name, state.subdir, kwargs)
+
+ if gresource: # Only one target for .gresource files
+ return [target_c]
h_kwargs = {
'command': cmd,
@@ -128,6 +173,10 @@ can not be used with the current version of glib-compiled-resources, due to
# The header doesn't actually care about the files yet it errors if missing
'depends': depends
}
+ if install_header:
+ h_kwargs['install'] = install_header
+ h_kwargs['install_dir'] = kwargs.get('install_dir',
+ state.environment.coredata.get_builtin_option('includedir'))
target_h = build.CustomTarget(args[0] + '_h', state.subdir, h_kwargs)
return [target_c, target_h]
@@ -206,9 +255,13 @@ can not be used with the current version of glib-compiled-resources, due to
return dep_files, depends, subdirs
- @staticmethod
- def _get_link_args(state, lib, depends=None):
- link_command = ['-l%s' % lib.name]
+
+ def _get_link_args(self, state, lib, depends=None):
+ if gir_has_extra_lib_arg():
+ link_command = ['--extra-library=%s' % lib.name]
+ else:
+ link_command = ['-l%s' % lib.name]
+ print('lib: %s - %s' % (lib.name, link_command))
if isinstance(lib, build.SharedLibrary):
link_command += ['-L%s' %
os.path.join(state.environment.get_build_dir(),
@@ -288,6 +341,8 @@ can not be used with the current version of glib-compiled-resources, due to
# Hack to avoid passing some compiler options in
if lib.startswith("-W"):
continue
+ if gir_has_extra_lib_arg():
+ lib = lib.replace('-l', '--extra-library=')
ldflags.update([lib])
if isinstance(dep, dependencies.PkgConfigDependency):
@@ -308,6 +363,8 @@ can not be used with the current version of glib-compiled-resources, due to
raise MesonException('Gir takes one argument')
if kwargs.get('install_dir'):
raise MesonException('install_dir is not supported with generate_gir(), see "install_dir_gir" and "install_dir_typelib"')
+ giscanner = find_program('g-ir-scanner', 'Gir')
+ gicompiler = find_program('g-ir-compiler', 'Gir')
girtarget = args[0]
while hasattr(girtarget, 'held_object'):
girtarget = girtarget.held_object
@@ -329,7 +386,7 @@ can not be used with the current version of glib-compiled-resources, due to
depends = [girtarget]
gir_inc_dirs = []
- scan_command = ['g-ir-scanner', '@INPUT@']
+ scan_command = giscanner.get_command() + ['@INPUT@']
scan_command += pkgargs
scan_command += ['--no-libtool', '--namespace='+ns, '--nsversion=' + nsversion, '--warn-all',
'--output', '@OUTPUT@']
@@ -484,7 +541,7 @@ can not be used with the current version of glib-compiled-resources, due to
scan_target = GirTarget(girfile, state.subdir, scankwargs)
typelib_output = '%s-%s.typelib' % (ns, nsversion)
- typelib_cmd = ['g-ir-compiler', scan_target, '--output', '@OUTPUT@']
+ typelib_cmd = gicompiler.get_command() + [scan_target, '--output', '@OUTPUT@']
typelib_cmd += self._get_include_args(state, gir_inc_dirs,
prefix='--includedir=')
for incdir in typelib_includes:
@@ -506,7 +563,9 @@ can not be used with the current version of glib-compiled-resources, due to
raise MesonException('Compile_schemas does not take positional arguments.')
srcdir = os.path.join(state.build_to_src, state.subdir)
outdir = state.subdir
- cmd = ['glib-compile-schemas', '--targetdir', outdir, srcdir]
+
+ cmd = find_program('glib-compile-schemas', 'gsettings-compile').get_command()
+ cmd += ['--targetdir', outdir, srcdir]
kwargs['command'] = cmd
kwargs['input'] = []
kwargs['output'] = 'gschemas.compiled'
@@ -684,7 +743,8 @@ can not be used with the current version of glib-compiled-resources, due to
raise MesonException('Gdbus_codegen takes two arguments, name and xml file.')
namebase = args[0]
xml_file = args[1]
- cmd = ['gdbus-codegen']
+ target_name = namebase + '-gdbus'
+ cmd = find_program('gdbus-codegen', target_name).get_command()
if 'interface_prefix' in kwargs:
cmd += ['--interface-prefix', kwargs.pop('interface_prefix')]
if 'namespace' in kwargs:
@@ -695,7 +755,7 @@ can not be used with the current version of glib-compiled-resources, due to
'output' : outputs,
'command' : cmd
}
- return build.CustomTarget(namebase + '-gdbus', state.subdir, custom_kwargs)
+ return build.CustomTarget(target_name, state.subdir, custom_kwargs)
def mkenums(self, state, args, kwargs):
if len(args) != 1:
@@ -741,7 +801,7 @@ can not be used with the current version of glib-compiled-resources, due to
elif arg not in known_custom_target_kwargs:
raise MesonException(
'Mkenums does not take a %s keyword argument.' % (arg, ))
- cmd = ['glib-mkenums'] + cmd
+ cmd = find_program('glib-mkenums', 'mkenums').get_command() + cmd
custom_kwargs = {}
for arg in known_custom_target_kwargs:
if arg in kwargs:
@@ -822,7 +882,7 @@ can not be used with the current version of glib-compiled-resources, due to
raise MesonException(
'Sources keyword argument must be a string or array.')
- cmd = ['glib-genmarshal']
+ cmd = find_program('glib-genmarshal', output + '_genmarshal').get_command()
known_kwargs = ['internal', 'nostdinc', 'skip_source', 'stdinc',
'valist_marshallers']
known_custom_target_kwargs = ['build_always', 'depends',
@@ -949,7 +1009,8 @@ can not be used with the current version of glib-compiled-resources, due to
build_dir = os.path.join(state.environment.get_build_dir(), state.subdir)
source_dir = os.path.join(state.environment.get_source_dir(), state.subdir)
pkg_cmd, vapi_depends, vapi_packages, vapi_includes = self._extract_vapi_packages(state, kwargs)
- cmd = ['vapigen', '--quiet', '--library=' + library, '--directory=' + build_dir]
+ cmd = find_program('vapigen', 'Vaapi').get_command()
+ cmd += ['--quiet', '--library=' + library, '--directory=' + build_dir]
cmd += self._vapi_args_to_command('--vapidir=', 'vapi_dirs', kwargs)
cmd += self._vapi_args_to_command('--metadatadir=', 'metadata_dirs', kwargs)
cmd += self._vapi_args_to_command('--girdir=', 'gir_dirs', kwargs)
diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py
index 1ddb2fc..13394c1 100644
--- a/mesonbuild/modules/i18n.py
+++ b/mesonbuild/modules/i18n.py
@@ -12,21 +12,81 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from os import path
from .. import coredata, mesonlib, build
+from ..mesonlib import MesonException
import sys
+PRESET_ARGS = {
+ 'glib': [
+ '--from-code=UTF-8',
+ '--add-comments',
+
+ # https://developer.gnome.org/glib/stable/glib-I18N.html
+ '--keyword=_',
+ '--keyword=N_',
+ '--keyword=C_:1c,2',
+ '--keyword=NC_:1c,2',
+ '--keyword=g_dcgettext:2',
+ '--keyword=g_dngettext:2,3',
+ '--keyword=g_dpgettext2:2c,3',
+
+ '--flag=N_:1:pass-c-format',
+ '--flag=C_:2:pass-c-format',
+ '--flag=NC_:2:pass-c-format',
+ '--flag=g_dngettext:2:pass-c-format',
+ '--flag=g_strdup_printf:1:c-format',
+ '--flag=g_string_printf:2:c-format',
+ '--flag=g_string_append_printf:2:c-format',
+ '--flag=g_error_new:3:c-format',
+ '--flag=g_set_error:4:c-format',
+ ]
+}
+
class I18nModule:
+ def merge_file(self, state, args, kwargs):
+ podir = kwargs.pop('po_dir', None)
+ if not podir:
+ raise MesonException('i18n: po_dir is a required kwarg')
+ podir = path.join(state.build_to_src, state.subdir, podir)
+
+ file_type = kwargs.pop('type', 'xml')
+ VALID_TYPES = ('xml', 'desktop')
+ if not file_type in VALID_TYPES:
+ raise MesonException('i18n: "{}" is not a valid type {}'.format(file_type, VALID_TYPES))
+
+ kwargs['command'] = ['msgfmt', '--' + file_type,
+ '--template', '@INPUT@', '-d', podir, '-o', '@OUTPUT@']
+ return build.CustomTarget(kwargs['output'] + '_merge', state.subdir, kwargs)
+
+ @staticmethod
+ def _read_linguas(state):
+ linguas = path.join(state.environment.get_source_dir(), state.subdir, 'LINGUAS')
+ try:
+ with open(linguas) as f:
+ return [line.strip() for line in f if not line.strip().startswith('#')]
+ except (FileNotFoundError, PermissionError):
+ return []
+
def gettext(self, state, args, kwargs):
if len(args) != 1:
raise coredata.MesonException('Gettext requires one positional argument (package name).')
packagename = args[0]
- languages = mesonlib.stringlistify(kwargs.get('languages', []))
+ languages = mesonlib.stringlistify(kwargs.get('languages', self._read_linguas(state)))
if len(languages) == 0:
raise coredata.MesonException('List of languages empty.')
datadirs = mesonlib.stringlistify(kwargs.get('data_dirs', []))
extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+ preset = kwargs.pop('preset', None)
+ if preset:
+ preset_args = PRESET_ARGS.get(preset)
+ if not preset_args:
+ raise coredata.MesonException('i18n: Preset "{}" is not one of the valid options: {}'.format(
+ preset, list(PRESET_ARGS.keys())))
+ extra_args = set(preset_args + extra_args)
+
pkg_arg = '--pkgname=' + packagename
lang_arg = '--langs=' + '@@'.join(languages)
datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
index 216cd03..f74f9e9 100644
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -49,6 +49,7 @@ class PkgConfigModule:
# 'os.path.join' for details)
ofile.write('libdir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('libdir')))
ofile.write('includedir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('includedir')))
+ ofile.write('\n')
ofile.write('Name: %s\n' % name)
if len(description) > 0:
ofile.write('Description: %s\n' % description)
diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py
index f593c8e..ad1fedd 100644
--- a/mesonbuild/mparser.py
+++ b/mesonbuild/mparser.py
@@ -22,10 +22,12 @@ class ParseException(MesonException):
self.colno = colno
class Token:
- def __init__(self, tid, lineno, colno, value):
+ def __init__(self, tid, subdir, lineno, colno, bytespan, value):
self.tid = tid
+ self.subdir = subdir
self.lineno = lineno
self.colno = colno
+ self.bytespan = bytespan
self.value = value
def __eq__(self, other):
@@ -71,7 +73,7 @@ class Lexer:
('questionmark', re.compile(r'\?')),
]
- def lex(self, code):
+ def lex(self, code, subdir):
lineno = 1
line_start = 0
loc = 0;
@@ -87,7 +89,10 @@ class Lexer:
curline = lineno
col = mo.start()-line_start
matched = True
+ span_start = loc
loc = mo.end()
+ span_end = loc
+ bytespan = (span_start, span_end)
match_text = mo.group()
if tid == 'ignore' or tid == 'comment':
break
@@ -123,40 +128,41 @@ class Lexer:
tid = match_text
else:
value = match_text
- yield Token(tid, curline, col, value)
+ yield Token(tid, subdir, curline, col, bytespan, value)
break
if not matched:
raise ParseException('lexer', lineno, col)
-class BooleanNode:
- def __init__(self, token, value):
+class ElementaryNode:
+ def __init__(self, token):
self.lineno = token.lineno
+ self.subdir = token.subdir
self.colno = token.colno
+ self.value = token.value
+ self.bytespan = token.bytespan
+
+class BooleanNode(ElementaryNode):
+ def __init__(self, token, value):
+ super().__init__(token)
self.value = value
assert(isinstance(self.value, bool))
-class IdNode:
+class IdNode(ElementaryNode):
def __init__(self, token):
- self.lineno = token.lineno
- self.colno = token.colno
- self.value = token.value
+ super().__init__(token)
assert(isinstance(self.value, str))
def __str__(self):
return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
-class NumberNode:
+class NumberNode(ElementaryNode):
def __init__(self, token):
- self.lineno = token.lineno
- self.colno = token.colno
- self.value = token.value
+ super().__init__(token)
assert(isinstance(self.value, int))
-class StringNode:
+class StringNode(ElementaryNode):
def __init__(self, token):
- self.lineno = token.lineno
- self.colno = token.colno
- self.value = token.value
+ super().__init__(token)
assert(isinstance(self.value, str))
def __str__(self):
@@ -164,20 +170,23 @@ class StringNode:
class ArrayNode:
def __init__(self, args):
+ self.subdir = args.subdir
self.lineno = args.lineno
self.colno = args.colno
self.args = args
class EmptyNode:
def __init__(self):
+ self.subdir =''
self.lineno = 0
self.colno = 0
self.value = None
class OrNode:
- def __init__(self, lineno, colno, left, right):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self, left, right):
+ self.subdir = left.subdir
+ self.lineno = left.lineno
+ self.colno = left.colno
self.left = left
self.right = right
@@ -189,42 +198,48 @@ class AndNode:
self.right = right
class ComparisonNode:
- def __init__(self, lineno, colno, ctype, left, right):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self, ctype, left, right):
+ self.lineno = left.lineno
+ self.colno = left.colno
+ self.subdir = left.subdir
self.left = left
self.right = right
self.ctype = ctype
class ArithmeticNode:
- def __init__(self, lineno, colno, operation, left, right):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self,operation, left, right):
+ self.subdir = left.subdir
+ self.lineno = left.lineno
+ self.colno = left.colno
self.left = left
self.right = right
self.operation = operation
class NotNode:
- def __init__(self, lineno, colno, value):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self, location_node, value):
+ self.subdir = location_node.subdir
+ self.lineno = location_node.lineno
+ self.colno = location_node.colno
self.value = value
class CodeBlockNode:
- def __init__(self, lineno, colno):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self, location_node):
+ self.subdir = location_node.subdir
+ self.lineno = location_node.lineno
+ self.colno = location_node.colno
self.lines = []
class IndexNode:
def __init__(self, iobject, index):
self.iobject = iobject
self.index = index
+ self.subdir = iobject.subdir
self.lineno = iobject.lineno
self.colno = iobject.colno
class MethodNode:
- def __init__(self, lineno, colno, source_object, name, args):
+ def __init__(self, subdir, lineno, colno, source_object, name, args):
+ self.subdir = subdir
self.lineno = lineno
self.colno = colno
self.source_object = source_object
@@ -233,7 +248,8 @@ class MethodNode:
self.args = args
class FunctionNode:
- def __init__(self, lineno, colno, func_name, args):
+ def __init__(self, subdir, lineno, colno, func_name, args):
+ self.subdir = subdir
self.lineno = lineno
self.colno = colno
self.func_name = func_name
@@ -272,9 +288,10 @@ class IfClauseNode():
self.elseblock = EmptyNode()
class UMinusNode():
- def __init__(self, lineno, colno, value):
- self.lineno = lineno
- self.colno = colno
+ def __init__(self, current_location, value):
+ self.subdir = current_location.subdir
+ self.lineno = current_location.lineno
+ self.colno = current_location.colno
self.value = value
class IfNode():
@@ -296,7 +313,9 @@ class ArgumentNode():
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
+ self.subdir = token.subdir
self.arguments = []
+ self.commas = []
self.kwargs = {}
self.order_error = False
@@ -351,8 +370,8 @@ comparison_map = {'equal': '==',
# 9 plain token
class Parser:
- def __init__(self, code):
- self.stream = Lexer().lex(code)
+ def __init__(self, code, subdir):
+ self.stream = Lexer().lex(code, subdir)
self.getsym()
self.in_ternary = False
@@ -360,7 +379,7 @@ class Parser:
try:
self.current = next(self.stream)
except StopIteration:
- self.current = Token('eof', 0, 0, None)
+ self.current = Token('eof', '', 0, 0, (0, 0), None)
def accept(self, s):
if self.current.tid == s:
@@ -409,7 +428,7 @@ class Parser:
def e2(self):
left = self.e3()
while self.accept('or'):
- left = OrNode(left.lineno, left.colno, left, self.e3())
+ left = OrNode(left, self.e3())
return left
def e3(self):
@@ -422,7 +441,7 @@ class Parser:
left = self.e5()
for nodename, operator_type in comparison_map.items():
if self.accept(nodename):
- return ComparisonNode(left.lineno, left.colno, operator_type, left, self.e5())
+ return ComparisonNode(operator_type, left, self.e5())
return left
def e5(self):
@@ -431,38 +450,38 @@ class Parser:
def e5add(self):
left = self.e5sub()
if self.accept('plus'):
- return ArithmeticNode(left.lineno, left.colno, 'add', left, self.e5add())
+ return ArithmeticNode('add', left, self.e5add())
return left
def e5sub(self):
left = self.e5mod()
if self.accept('dash'):
- return ArithmeticNode(left.lineno, left.colno, 'sub', left, self.e5sub())
+ return ArithmeticNode('sub', left, self.e5sub())
return left
def e5mod(self):
left = self.e5mul()
if self.accept('percent'):
- return ArithmeticNode(left.lineno, left.colno, 'mod', left, self.e5mod())
+ return ArithmeticNode('mod', left, self.e5mod())
return left
def e5mul(self):
left = self.e5div()
if self.accept('star'):
- return ArithmeticNode(left.lineno, left.colno, 'mul', left, self.e5mul())
+ return ArithmeticNode('mul', left, self.e5mul())
return left
def e5div(self):
left = self.e6()
if self.accept('fslash'):
- return ArithmeticNode(left.lineno, left.colno, 'div', left, self.e5div())
+ return ArithmeticNode('div', left, self.e5div())
return left
def e6(self):
if self.accept('not'):
- return NotNode(self.current.lineno, self.current.colno, self.e7())
+ return NotNode(self.current, self.e7())
if self.accept('dash'):
- return UMinusNode(self.current.lineno, self.current.colno, self.e7())
+ return UMinusNode(self.current, self.e7())
return self.e7()
def e7(self):
@@ -473,7 +492,7 @@ class Parser:
if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id',
left.lineno, left.colno)
- left = FunctionNode(left.lineno, left.colno, left.value, args)
+ left = FunctionNode(left.subdir, left.lineno, left.colno, left.value, args)
go_again = True
while go_again:
go_again = False
@@ -516,15 +535,19 @@ class Parser:
a = ArgumentNode(s)
while not isinstance(s, EmptyNode):
+ potential = self.current
if self.accept('comma'):
+ a.commas.append(potential)
a.append(s)
elif self.accept('colon'):
if not isinstance(s, IdNode):
raise ParseException('Keyword argument must be a plain identifier.',
s.lineno, s.colno)
a.set_kwarg(s.value, self.statement())
+ potential = self.current
if not self.accept('comma'):
return a
+ a.commas.append(potential)
else:
a.append(s)
return a
@@ -539,7 +562,7 @@ class Parser:
self.expect('lparen')
args = self.args()
self.expect('rparen')
- method = MethodNode(methodname.lineno, methodname.colno, source_object, methodname.value, args)
+ method = MethodNode(methodname.subdir, methodname.lineno, methodname.colno, source_object, methodname.value, args)
if self.accept('dot'):
return self.method_call(method)
return method
@@ -593,7 +616,7 @@ class Parser:
return self.statement()
def codeblock(self):
- block = CodeBlockNode(self.current.lineno, self.current.colno)
+ block = CodeBlockNode(self.current)
cond = True
while cond:
curline = self.line()
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index 9f57fd6..4fe0843 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -79,7 +79,7 @@ class OptionInterpreter:
def process(self, option_file):
try:
with open(option_file, 'r', encoding='utf8') as f:
- ast = mparser.Parser(f.read()).parse()
+ ast = mparser.Parser(f.read(), '').parse()
except mesonlib.MesonException as me:
me.file = option_file
raise me
diff --git a/mesonrewriter.py b/mesonrewriter.py
new file mode 100755
index 0000000..fb85745
--- /dev/null
+++ b/mesonrewriter.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# Copyright 2016 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.
+
+# This tool is used to manipulate an existing Meson build definition.
+#
+# - add a file to a target
+# - remove files from a target
+# - move targets
+# - reindent?
+
+import mesonbuild.astinterpreter
+from mesonbuild.mesonlib import MesonException
+from mesonbuild import mlog
+import sys, traceback
+import argparse
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('--sourcedir', default='.',
+ help='Path to source directory.')
+parser.add_argument('--target', default=None,
+ help='Name of target to edit.')
+parser.add_argument('--filename', default=None,
+ help='Name of source file to add or remove to target.')
+parser.add_argument('commands', nargs='+')
+
+if __name__ == '__main__':
+ options = parser.parse_args()
+ if options.target is None or options.filename is None:
+ sys.exit("Must specify both target and filename.")
+ print('This tool is highly experimental, use with care.')
+ rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '')
+ try:
+ if options.commands[0] == 'add':
+ rewriter.add_source(options.target, options.filename)
+ elif options.commands[0] == 'remove':
+ rewriter.remove_source(options.target, options.filename)
+ else:
+ sys.exit('Unknown command: ' + options.commands[0])
+ except Exception as e:
+ if isinstance(e, MesonException):
+ if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'):
+ mlog.log(mlog.red('\nMeson encountered an error in file %s, line %d, column %d:' % (e.file, e.lineno, e.colno)))
+ else:
+ mlog.log(mlog.red('\nMeson encountered an error:'))
+ mlog.log(e)
+ else:
+ traceback.print_exc()
+ sys.exit(1)
diff --git a/mesontest.py b/mesontest.py
index 73c92e4..2d834b1 100755
--- a/mesontest.py
+++ b/mesontest.py
@@ -54,6 +54,8 @@ def determine_worker_count():
parser = argparse.ArgumentParser()
parser.add_argument('--repeat', default=1, dest='repeat', type=int,
help='Number of times to run the tests.')
+parser.add_argument('--no-rebuild', default=False, action='store_true',
+ help='Do not rebuild before running tests.')
parser.add_argument('--gdb', default=False, dest='gdb', action='store_true',
help='Run test under gdb.')
parser.add_argument('--list', default=False, dest='list', action='store_true',
@@ -76,6 +78,10 @@ parser.add_argument('--num-processes', default=determine_worker_count(), type=in
help='How many parallel processes to use.')
parser.add_argument('-v', '--verbose', default=False, action='store_true',
help='Do not redirect stdout and stderr')
+parser.add_argument('-t', '--timeout-multiplier', type=float, default=1.0,
+ help='Define a multiplier for test timeout, for example '
+ ' when running tests in particular conditions they might take'
+ ' more time to execute.')
parser.add_argument('args', nargs='*')
class TestRun():
@@ -142,13 +148,35 @@ class TestHarness:
self.collected_logs = []
self.error_count = 0
self.is_run = False
+ self.cant_rebuild = False
if self.options.benchmark:
self.datafile = os.path.join(options.wd, 'meson-private/meson_benchmark_setup.dat')
else:
self.datafile = os.path.join(options.wd, 'meson-private/meson_test_setup.dat')
- print(self.datafile)
+
+ def rebuild_all(self):
+ if not os.path.isfile(os.path.join(self.options.wd, 'build.ninja')):
+ print("Only ninja backend is supported to rebuilt tests before running them.")
+ self.cant_rebuild = True
+ return True
+
+ ninja = environment.detect_ninja()
+ if not ninja:
+ print("Can't find ninja, can't rebuild test.")
+ self.cant_rebuild = True
+ return False
+
+ p = subprocess.Popen([ninja, '-C', self.options.wd])
+ (stdo, stde) = p.communicate()
+
+ if p.returncode != 0:
+ print("Could not rebuild")
+ return False
+
+ return True
def run_single_test(self, wrap, test):
+ failling = False
if test.fname[0].endswith('.jar'):
cmd = ['java', '-jar'] + test.fname
elif not test.is_cross and run_with_mono(test.fname[0]):
@@ -198,9 +226,12 @@ class TestHarness:
cwd=test.workdir,
preexec_fn=setsid)
timed_out = False
+ timeout = test.timeout * self.options.timeout_multiplier
try:
- (stdo, stde) = p.communicate(timeout=test.timeout)
+ (stdo, stde) = p.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
+ if self.options.verbose:
+ print("%s time out (After %d seconds)" % (test.name, timeout))
timed_out = True
# Python does not provide multiplatform support for
# killing a process and all its children so we need
@@ -217,6 +248,7 @@ class TestHarness:
stde = decode(stde)
if timed_out:
res = 'TIMEOUT'
+ failling = True
if p.returncode == GNU_SKIP_RETURNCODE:
res = 'SKIP'
elif (not test.should_fail and p.returncode == 0) or \
@@ -224,8 +256,14 @@ class TestHarness:
res = 'OK'
else:
res = 'FAIL'
+ failling = True
returncode = p.returncode
- return TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env)
+ result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env)
+
+ if failling:
+ self.failed_tests.append(result)
+
+ return result
def print_stats(self, numlen, tests, name, result, i, logfile, jsonlogfile):
startpad = ' '*(numlen - len('%d' % (i+1)))
@@ -251,7 +289,7 @@ class TestHarness:
print('Test data file. Probably this means that you did not run this in the build directory.')
return 1
self.is_run = True
- logfilename = self.run_tests(self.datafile, self.options.logbase)
+ logfilename = self.run_tests(self.options.logbase)
if len(self.collected_logs) > 0:
if len(self.collected_logs) > 10:
print('\nThe output from 10 first failed tests:\n')
@@ -268,7 +306,15 @@ class TestHarness:
print('Full log written to %s.' % logfilename)
return self.error_count
- def run_tests(self, datafilename, log_base):
+ def get_tests(self):
+ with open(self.datafile, 'rb') as f:
+ tests = pickle.load(f)
+ for test in tests:
+ test.rebuilt = False
+
+ return tests
+
+ def run_tests(self, log_base):
logfile_base = os.path.join(self.options.wd, 'meson-logs', log_base)
if self.options.wrapper is None:
wrap = []
@@ -279,8 +325,7 @@ class TestHarness:
namebase = wrap[0]
logfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.txt'
jsonlogfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.json'
- with open(datafilename, 'rb') as f:
- tests = pickle.load(f)
+ tests = self.get_tests()
if len(tests) == 0:
print('No tests defined.')
return
@@ -298,27 +343,28 @@ class TestHarness:
logfile.write('Log of Meson test suite run on %s.\n\n' %
datetime.datetime.now().isoformat())
- for i, test in enumerate(filtered_tests):
- if test.suite[0] == '':
- visible_name = test.name
- else:
- if self.options.suite is not None:
- visible_name = self.options.suite + ' / ' + test.name
+ for i in range(self.options.repeat):
+ for i, test in enumerate(filtered_tests):
+ if test.suite[0] == '':
+ visible_name = test.name
else:
- visible_name = test.suite[0] + ' / ' + test.name
-
- if not test.is_parallel:
- self.drain_futures(futures)
- futures = []
- res = self.run_single_test(wrap, test)
- if not self.options.verbose:
- self.print_stats(numlen, filtered_tests, visible_name, res, i,
- logfile, jsonlogfile)
- else:
- f = executor.submit(self.run_single_test, wrap, test)
- if not self.options.verbose:
- futures.append((f, numlen, filtered_tests, visible_name, i,
- logfile, jsonlogfile))
+ if self.options.suite is not None:
+ visible_name = self.options.suite + ' / ' + test.name
+ else:
+ visible_name = test.suite[0] + ' / ' + test.name
+
+ if not test.is_parallel:
+ self.drain_futures(futures)
+ futures = []
+ res = self.run_single_test(wrap, test)
+ if not self.options.verbose:
+ self.print_stats(numlen, filtered_tests, visible_name, res, i,
+ logfile, jsonlogfile)
+ else:
+ f = executor.submit(self.run_single_test, wrap, test)
+ if not self.options.verbose:
+ futures.append((f, numlen, filtered_tests, visible_name, i,
+ logfile, jsonlogfile))
self.drain_futures(futures, logfile, jsonlogfile)
finally:
if jsonlogfile:
@@ -332,8 +378,23 @@ class TestHarness:
def drain_futures(self, futures, logfile, jsonlogfile):
for i in futures:
(result, numlen, tests, name, i, logfile, jsonlogfile) = i
- if not self.options.verbose:
+ if self.options.repeat > 1 and self.failed_tests:
+ result.cancel()
+ elif not self.options.verbose:
self.print_stats(numlen, tests, name, result.result(), i, logfile, jsonlogfile)
+ else:
+ result.result()
+
+ if self.options.repeat > 1 and self.failed_tests:
+ if not self.options.verbose:
+ for res in self.failed_tests:
+ print('Test failed:\n\n-- stdout --\n')
+ print(res.stdo)
+ print('\n-- stderr --\n')
+ print(res.stde)
+ return 1
+
+ return
def run_special(self):
'Tests run by the user, usually something like "under gdb 1000 times".'
@@ -348,7 +409,7 @@ class TestHarness:
return 1
if os.path.isfile('build.ninja'):
subprocess.check_call([environment.detect_ninja(), 'all'])
- tests = pickle.load(open(self.datafile, 'rb'))
+ tests = self.get_tests()
if self.options.list:
for i in tests:
print(i.name)
@@ -358,9 +419,11 @@ class TestHarness:
for i in range(self.options.repeat):
print('Running: %s %d/%d' % (t.name, i+1, self.options.repeat))
if self.options.gdb:
- wrap = ['gdb', '--quiet', '-ex', 'run', '-ex', 'quit']
+ wrap = ['gdb', '--quiet']
if len(t.cmd_args) > 0:
wrap.append('--args')
+ if self.options.repeat > 1:
+ wrap.append('-ex', 'run', '-ex', 'quit')
res = self.run_single_test(wrap, t)
else:
@@ -389,9 +452,14 @@ def run(args):
if options.gdb:
options.verbose = True
+ options.wd = os.path.abspath(options.wd)
+
th = TestHarness(options)
if options.list:
return th.run_special()
+ if not options.no_rebuild:
+ if not th.rebuild_all():
+ return -1
elif len(options.args) == 0:
return th.doit()
return th.run_special()
diff --git a/run_project_tests.py b/run_project_tests.py
index dcc6006..da70bcb 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -211,8 +211,8 @@ def run_test_inprocess(testdir):
old_cwd = os.getcwd()
os.chdir(testdir)
try:
- returncode_test = mesontest.run([])
- returncode_benchmark = mesontest.run(['--benchmark', '--logbase', 'benchmarklog'])
+ returncode_test = mesontest.run(['--no-rebuild'])
+ returncode_benchmark = mesontest.run(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog'])
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
@@ -380,7 +380,14 @@ def run_tests(extra_args):
build_time = 0
test_time = 0
- executor = conc.ProcessPoolExecutor(max_workers=multiprocessing.cpu_count())
+ try:
+ # This fails in some CI environments for unknown reasons.
+ num_workers = multiprocessing.cpu_count()
+ except Exception as e:
+ print('Could not determine number of CPUs due to the following reason:' + str(e))
+ print('Defaulting to using only one process')
+ num_workers = 1
+ executor = conc.ProcessPoolExecutor(max_workers=num_workers)
for name, test_cases, skipped in all_tests:
current_suite = ET.SubElement(junit_root, 'testsuite', {'name' : name, 'tests' : str(len(test_cases))})
diff --git a/run_unittests.py b/run_unittests.py
index 39e93c9..36d899a 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -17,18 +17,21 @@ import unittest, os, sys, shutil, time
import subprocess
import re, json
import tempfile
+from glob import glob
import mesonbuild.environment
from mesonbuild.environment import detect_ninja
from mesonbuild.dependencies import PkgConfigDependency, Qt5Dependency
def get_soname(fname):
# HACK, fix to not use shell.
- raw_out = subprocess.check_output(['readelf', '-a', fname])
- pattern = re.compile(b'soname: \[(.*?)\]')
- for line in raw_out.split(b'\n'):
+ raw_out = subprocess.check_output(['readelf', '-a', fname],
+ universal_newlines=True)
+ pattern = re.compile('soname: \[(.*?)\]')
+ for line in raw_out.split('\n'):
m = pattern.search(line)
if m is not None:
return m.group(1)
+ raise RuntimeError('Could not determine soname:\n\n' + raw_out)
class FakeEnvironment(object):
def __init__(self):
@@ -52,6 +55,7 @@ class LinuxlikeTests(unittest.TestCase):
def setUp(self):
super().setUp()
src_root = os.path.dirname(__file__)
+ src_root = os.path.join(os.getcwd(), src_root)
self.builddir = tempfile.mkdtemp()
self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py')]
self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')]
@@ -60,6 +64,7 @@ class LinuxlikeTests(unittest.TestCase):
self.common_test_dir = os.path.join(src_root, 'test cases/common')
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
+ self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
self.output = b''
self.orig_env = os.environ.copy()
@@ -91,27 +96,55 @@ class LinuxlikeTests(unittest.TestCase):
with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f:
return f.readlines()
+ def get_meson_log_compiler_checks(self):
+ '''
+ Fetch a list command-lines run by meson for compiler checks.
+ Each command-line is returned as a list of arguments.
+ '''
+ log = self.get_meson_log()
+ prefix = 'Command line:'
+ cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
+ return cmds
+
def introspect(self, arg):
- out = subprocess.check_output(self.mintro_command + [arg, self.builddir])
- return json.loads(out.decode('utf-8'))
+ out = subprocess.check_output(self.mintro_command + [arg, self.builddir],
+ universal_newlines=True)
+ return json.loads(out)
def test_basic_soname(self):
+ '''
+ Test that the soname is set correctly for shared libraries. This can't
+ be an ordinary test case because we need to run `readelf` and actually
+ check the soname.
+ https://github.com/mesonbuild/meson/issues/785
+ '''
testdir = os.path.join(self.common_test_dir, '4 shared')
self.init(testdir)
self.build()
lib1 = os.path.join(self.builddir, 'libmylib.so')
soname = get_soname(lib1)
- self.assertEqual(soname, b'libmylib.so')
+ self.assertEqual(soname, 'libmylib.so')
def test_custom_soname(self):
+ '''
+ Test that the soname is set correctly for shared libraries when
+ a custom prefix and/or suffix is used. This can't be an ordinary test
+ case because we need to run `readelf` and actually check the soname.
+ https://github.com/mesonbuild/meson/issues/785
+ '''
testdir = os.path.join(self.common_test_dir, '27 library versions')
self.init(testdir)
self.build()
lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix')
soname = get_soname(lib1)
- self.assertEqual(soname, b'prefixsomelib.suffix')
+ self.assertEqual(soname, 'prefixsomelib.suffix')
def test_pic(self):
+ '''
+ Test that -fPIC is correctly added to static libraries when b_staticpic
+ is true and not when it is false. This can't be an ordinary test case
+ because we need to inspect the compiler database.
+ '''
testdir = os.path.join(self.common_test_dir, '3 static')
self.init(testdir)
compdb = self.get_compdb()
@@ -127,6 +160,12 @@ class LinuxlikeTests(unittest.TestCase):
self.assertTrue('-fPIC' not in compdb[0]['command'])
def test_pkgconfig_gen(self):
+ '''
+ Test that generated pkg-config files can be found and have the correct
+ version and link args. This can't be an ordinary test case because we
+ need to run pkg-config outside of a Meson build file.
+ https://github.com/mesonbuild/meson/issues/889
+ '''
testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen')
self.init(testdir)
env = FakeEnvironment()
@@ -138,6 +177,12 @@ class LinuxlikeTests(unittest.TestCase):
self.assertTrue('-lfoo' in simple_dep.get_link_args())
def test_vala_c_warnings(self):
+ '''
+ Test that no warnings are emitted for C code generated by Vala. This
+ can't be an ordinary test case because we need to inspect the compiler
+ database.
+ https://github.com/mesonbuild/meson/issues/864
+ '''
testdir = os.path.join(self.vala_test_dir, '5 target glib')
self.init(testdir)
compdb = self.get_compdb()
@@ -165,6 +210,12 @@ class LinuxlikeTests(unittest.TestCase):
self.assertTrue('-Werror' in c_command)
def test_static_compile_order(self):
+ '''
+ Test that the order of files in a compiler command-line while compiling
+ and linking statically is deterministic. This can't be an ordinary test
+ case because we need to inspect the compiler database.
+ https://github.com/mesonbuild/meson/pull/951
+ '''
testdir = os.path.join(self.common_test_dir, '5 linkstatic')
self.init(testdir)
compdb = self.get_compdb()
@@ -176,6 +227,10 @@ class LinuxlikeTests(unittest.TestCase):
# FIXME: We don't have access to the linker command
def test_install_introspection(self):
+ '''
+ Tests that the Meson introspection API exposes install filenames correctly
+ https://github.com/mesonbuild/meson/issues/829
+ '''
testdir = os.path.join(self.common_test_dir, '8 install')
self.init(testdir)
intro = self.introspect('--targets')
@@ -185,11 +240,19 @@ class LinuxlikeTests(unittest.TestCase):
self.assertEqual(intro[1]['install_filename'], '/usr/local/bin/prog')
def test_run_target_files_path(self):
+ '''
+ Test that run_targets are run from the correct directory
+ https://github.com/mesonbuild/meson/issues/957
+ '''
testdir = os.path.join(self.common_test_dir, '58 run target')
self.init(testdir)
self.run_target('check_exists')
def test_qt5dependency_qmake_detection(self):
+ '''
+ Test that qt5 detection with qmake works. This can't be an ordinary
+ test case because it involves setting the environment.
+ '''
# Verify that qmake is for Qt5
if not shutil.which('qmake-qt5'):
if not shutil.which('qmake'):
@@ -211,5 +274,130 @@ class LinuxlikeTests(unittest.TestCase):
mesonlog = self.get_meson_log()
self.assertTrue(msg in mesonlog or msg2 in mesonlog)
+ def get_soname(self, fname):
+ output = subprocess.check_output(['readelf', '-a', fname],
+ universal_newlines=True)
+ for line in output.split('\n'):
+ if 'SONAME' in line:
+ return line.split('[')[1].split(']')[0]
+ raise RuntimeError('Readelf gave no SONAME.')
+
+ def test_soname(self):
+ testdir = os.path.join(self.unit_test_dir, '1 soname')
+ self.init(testdir)
+ self.build()
+
+ # File without aliases set.
+ nover = os.path.join(self.builddir, 'libnover.so')
+ self.assertTrue(os.path.exists(nover))
+ self.assertFalse(os.path.islink(nover))
+ self.assertEqual(self.get_soname(nover), 'libnover.so')
+ self.assertEqual(len(glob(nover[:-3] + '*')), 1)
+
+ # File with version set
+ verset = os.path.join(self.builddir, 'libverset.so')
+ self.assertTrue(os.path.exists(verset + '.4.5.6'))
+ self.assertEqual(os.readlink(verset), 'libverset.so.4')
+ self.assertEqual(self.get_soname(verset), 'libverset.so.4')
+ self.assertEqual(len(glob(verset[:-3] + '*')), 3)
+
+ # File with soversion set
+ soverset = os.path.join(self.builddir, 'libsoverset.so')
+ self.assertTrue(os.path.exists(soverset + '.1.2.3'))
+ self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
+ self.assertEqual(self.get_soname(soverset), 'libsoverset.so.1.2.3')
+ self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
+
+ # File with version and soversion set to same values
+ settosame = os.path.join(self.builddir, 'libsettosame.so')
+ self.assertTrue(os.path.exists(settosame + '.7.8.9'))
+ self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
+ self.assertEqual(self.get_soname(settosame), 'libsettosame.so.7.8.9')
+ self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
+
+ # File with version and soversion set to different values
+ bothset = os.path.join(self.builddir, 'libbothset.so')
+ self.assertTrue(os.path.exists(bothset + '.1.2.3'))
+ self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
+ self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
+ self.assertEqual(self.get_soname(bothset), 'libbothset.so.1.2.3')
+ self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
+
+ def test_compiler_check_flags_order(self):
+ '''
+ Test that compiler check flags override all other flags. This can't be
+ an ordinary test case because it needs the environment to be set.
+ '''
+ Oflag = '-O3'
+ os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag
+ testdir = os.path.join(self.common_test_dir, '43 has function')
+ self.init(testdir)
+ cmds = self.get_meson_log_compiler_checks()
+ for cmd in cmds:
+ # Verify that -I flags from the `args` kwarg are first
+ # This is set in the '43 has function' test case
+ self.assertEqual(cmd[2], '-I/tmp')
+ # Verify that -O3 set via the environment is overriden by -O0
+ Oargs = [arg for arg in cmd if arg.startswith('-O')]
+ self.assertEqual(Oargs, [Oflag, '-O0'])
+
+class RewriterTests(unittest.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ src_root = os.path.dirname(__file__)
+ self.testroot = tempfile.mkdtemp()
+ self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')]
+ self.tmpdir = tempfile.mkdtemp()
+ self.workdir = os.path.join(self.tmpdir, 'foo')
+ self.test_dir = os.path.join(src_root, 'test cases/rewrite')
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+
+ def read_contents(self, fname):
+ with open(os.path.join(self.workdir, fname)) as f:
+ return f.read()
+
+ def check_effectively_same(self, mainfile, truth):
+ mf = self.read_contents(mainfile)
+ t = self.read_contents(truth)
+ # Rewriting is not guaranteed to do a perfect job of
+ # maintaining whitespace.
+ self.assertEqual(mf.replace(' ', ''), t.replace(' ', ''))
+
+ def prime(self, dirname):
+ shutil.copytree(os.path.join(self.test_dir, dirname), self.workdir)
+
+ def test_basic(self):
+ self.prime('1 basic')
+ subprocess.check_output(self.rewrite_command + ['remove',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir])
+ self.check_effectively_same('meson.build', 'removed.txt')
+ subprocess.check_output(self.rewrite_command + ['add',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir])
+ self.check_effectively_same('meson.build', 'added.txt')
+ subprocess.check_output(self.rewrite_command + ['remove',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir])
+ self.check_effectively_same('meson.build', 'removed.txt')
+
+ def test_subdir(self):
+ self.prime('2 subdirs')
+ top = self.read_contents('meson.build')
+ s2 = self.read_contents('sub2/meson.build')
+ subprocess.check_output(self.rewrite_command + ['remove',
+ '--target=something',
+ '--filename=second.c',
+ '--sourcedir', self.workdir])
+ self.check_effectively_same('sub1/meson.build', 'sub1/after.txt')
+ self.assertEqual(top, self.read_contents('meson.build'))
+ self.assertEqual(s2, self.read_contents('sub2/meson.build'))
+
if __name__ == '__main__':
unittest.main()
diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build
index 61f96e1..e0d3344 100644
--- a/test cases/common/43 has function/meson.build
+++ b/test cases/common/43 has function/meson.build
@@ -1,9 +1,12 @@
project('has function', 'c', 'cpp')
+# This is used in the `test_compiler_check_flags_order` unit test
+unit_test_args = '-I/tmp'
compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')]
foreach cc : compilers
- if not cc.has_function('printf', prefix : '#include<stdio.h>')
+ if not cc.has_function('printf', prefix : '#include<stdio.h>',
+ args : unit_test_args)
error('"printf" function not found (should always exist).')
endif
@@ -13,12 +16,16 @@ foreach cc : compilers
# On MSVC fprintf is defined as an inline function in the header, so it cannot
# be found without the include.
if cc.get_id() != 'msvc'
- assert(cc.has_function('fprintf'), '"fprintf" function not found without include (on !msvc).')
+ assert(cc.has_function('fprintf', args : unit_test_args),
+ '"fprintf" function not found without include (on !msvc).')
else
- assert(cc.has_function('fprintf', prefix : '#include <stdio.h>'), '"fprintf" function not found with include (on msvc).')
+ assert(cc.has_function('fprintf', prefix : '#include <stdio.h>',
+ args : unit_test_args),
+ '"fprintf" function not found with include (on msvc).')
endif
- if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>')
+ if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>',
+ args : unit_test_args)
error('Found non-existent function "hfkerhisadf".')
endif
@@ -28,16 +35,23 @@ foreach cc : compilers
# implemented in glibc it's probably not implemented in any other 'slimmer'
# C library variants either, so the check should be safe either way hopefully.
if host_machine.system() == 'linux' and cc.get_id() == 'gcc'
- assert (cc.has_function('poll', prefix : '#include <poll.h>'), 'couldn\'t detect "poll" when defined by a header')
- assert (not cc.has_function('lchmod', prefix : '''#include <sys/stat.h>
- #include <unistd.h>'''), '"lchmod" check should have failed')
+ assert (cc.has_function('poll', prefix : '#include <poll.h>',
+ args : unit_test_args),
+ 'couldn\'t detect "poll" when defined by a header')
+ lchmod_prefix = '#include <sys/stat.h>\n#include <unistd.h>'
+ assert (not cc.has_function('lchmod', prefix : lchmod_prefix,
+ args : unit_test_args),
+ '"lchmod" check should have failed')
endif
# For some functions one needs to define _GNU_SOURCE before including the
# right headers to get them picked up. Make sure we can detect these functions
# as well without any prefix
- if cc.has_header_symbol('sys/socket.h', 'recvmmsg', prefix : '#define _GNU_SOURCE')
+ if cc.has_header_symbol('sys/socket.h', 'recvmmsg',
+ prefix : '#define _GNU_SOURCE',
+ args : unit_test_args)
# We assume that if recvmmsg exists sendmmsg does too
- assert (cc.has_function('sendmmsg'), 'Failed to detect function "sendmmsg" (should always exist).')
+ assert (cc.has_function('sendmmsg', args : unit_test_args),
+ 'Failed to detect function "sendmmsg" (should always exist).')
endif
endforeach
diff --git a/test cases/common/51 pkgconfig-gen/meson.build b/test cases/common/51 pkgconfig-gen/meson.build
index 0933238..e1e41d9 100644
--- a/test cases/common/51 pkgconfig-gen/meson.build
+++ b/test cases/common/51 pkgconfig-gen/meson.build
@@ -19,11 +19,17 @@ pkgg.generate(
)
pkgconfig = find_program('pkg-config', required: false)
-if pkgconfig.found() and build_machine.system() != 'windows'
- test('pkgconfig-validation', pkgconfig,
- args: ['--validate', 'simple'],
- env: ['PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ],
- )
+if pkgconfig.found()
+ v = run_command(pkgconfig, '--version').stdout().strip()
+ if v.version_compare('>=0.29')
+ test('pkgconfig-validation', pkgconfig,
+ args: ['--validate', 'simple'],
+ env: ['PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ])
+ else
+ message('pkg-config version \'' + v + '\' too old, skipping validate test')
+ endif
+else
+ message('pkg-config not found, skipping validate test')
endif
# Test that name_prefix='' and name='libfoo' results in '-lfoo'
diff --git a/test cases/failing/37 pkgconfig dependency impossible conditions/meson.build b/test cases/failing/37 pkgconfig dependency impossible conditions/meson.build
new file mode 100644
index 0000000..54d434c
--- /dev/null
+++ b/test cases/failing/37 pkgconfig dependency impossible conditions/meson.build
@@ -0,0 +1,3 @@
+project('impossible-dep-test', 'c', version : '1.0')
+
+dependency('zlib', version : ['>=1.0', '<1.0'])
diff --git a/test cases/frameworks/6 gettext/data/meson.build b/test cases/frameworks/6 gettext/data/meson.build
new file mode 100644
index 0000000..d927ba3
--- /dev/null
+++ b/test cases/frameworks/6 gettext/data/meson.build
@@ -0,0 +1,8 @@
+i18n.merge_file(
+ input: 'test.desktop.in',
+ output: 'test.desktop',
+ type: 'desktop',
+ po_dir: '../po',
+ install: true,
+ install_dir: join_paths(get_option('datadir'), 'applications')
+)
diff --git a/test cases/frameworks/6 gettext/data/test.desktop.in b/test cases/frameworks/6 gettext/data/test.desktop.in
new file mode 100644
index 0000000..33b9a9f
--- /dev/null
+++ b/test cases/frameworks/6 gettext/data/test.desktop.in
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Name=Test
+GenericName=Application
+Comment=Test Application
+Type=Application
+
diff --git a/test cases/frameworks/6 gettext/installed_files.txt b/test cases/frameworks/6 gettext/installed_files.txt
index c95b9fd..ffe543f 100644
--- a/test cases/frameworks/6 gettext/installed_files.txt
+++ b/test cases/frameworks/6 gettext/installed_files.txt
@@ -1,3 +1,4 @@
usr/bin/intlprog
usr/share/locale/de/LC_MESSAGES/intltest.mo
usr/share/locale/fi/LC_MESSAGES/intltest.mo
+usr/share/applications/test.desktop
diff --git a/test cases/frameworks/6 gettext/meson.build b/test cases/frameworks/6 gettext/meson.build
index 6bba7e0..6b517a4 100644
--- a/test cases/frameworks/6 gettext/meson.build
+++ b/test cases/frameworks/6 gettext/meson.build
@@ -4,3 +4,4 @@ i18n = import('i18n')
subdir('po')
subdir('src')
+subdir('data')
diff --git a/test cases/frameworks/6 gettext/po/LINGUAS b/test cases/frameworks/6 gettext/po/LINGUAS
new file mode 100644
index 0000000..d319e48
--- /dev/null
+++ b/test cases/frameworks/6 gettext/po/LINGUAS
@@ -0,0 +1,2 @@
+de
+fi
diff --git a/test cases/frameworks/6 gettext/po/POTFILES b/test cases/frameworks/6 gettext/po/POTFILES
index 5fd4b84..f49cecd 100644
--- a/test cases/frameworks/6 gettext/po/POTFILES
+++ b/test cases/frameworks/6 gettext/po/POTFILES
@@ -1 +1,2 @@
src/intlmain.c
+data/test.desktop
diff --git a/test cases/frameworks/7 gnome/installed_files.txt b/test cases/frameworks/7 gnome/installed_files.txt
index 06f4163..d0d51d5 100644
--- a/test cases/frameworks/7 gnome/installed_files.txt
+++ b/test cases/frameworks/7 gnome/installed_files.txt
@@ -12,3 +12,5 @@ usr/share/gir-1.0/Meson-1.0.gir
usr/share/gir-1.0/MesonDep1-1.0.gir
usr/share/gir-1.0/MesonDep2-1.0.gir
usr/share/glib-2.0/schemas/com.github.meson.gschema.xml
+usr/share/simple-resources.gresource
+usr/include/simple-resources.h
diff --git a/test cases/frameworks/7 gnome/resources/meson.build b/test cases/frameworks/7 gnome/resources/meson.build
index f17e469..2e72501 100644
--- a/test cases/frameworks/7 gnome/resources/meson.build
+++ b/test cases/frameworks/7 gnome/resources/meson.build
@@ -3,6 +3,8 @@
simple_resources = gnome.compile_resources('simple-resources',
'simple.gresource.xml',
+ install_header : true,
+ export : true,
source_dir : '../resources-data',
c_name : 'simple_resources')
@@ -11,6 +13,15 @@ simple_res_exe = executable('simple-resources-test',
dependencies: gio)
test('simple resource test', simple_res_exe)
+gnome.compile_resources('simple-resources',
+ 'simple.gresource.xml',
+ gresource_bundle: true,
+ install: true,
+ install_dir: get_option('datadir'),
+ source_dir : '../resources-data',
+)
+test('simple resource test (gresource)', find_program('resources.py'))
+
if glib.version() >= '2.52.0'
# This test cannot pass if GLib version is older than 9.99.9.
# Meson will raise an error if the user tries to use the 'dependencies'
diff --git a/test cases/frameworks/7 gnome/resources/resources.py b/test cases/frameworks/7 gnome/resources/resources.py
new file mode 100644
index 0000000..b351b04
--- /dev/null
+++ b/test cases/frameworks/7 gnome/resources/resources.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+import os
+from gi.repository import Gio
+
+if __name__ == '__main__':
+ res = Gio.resource_load(os.path.join('resources', 'simple-resources.gresource'))
+ Gio.Resource._register(res)
+
+ data = Gio.resources_lookup_data('/com/example/myprog/res1.txt', Gio.ResourceLookupFlags.NONE)
+ assert(data.get_data() == b'This is a resource.\n')
diff --git a/test cases/linuxlike/1 pkg-config/meson.build b/test cases/linuxlike/1 pkg-config/meson.build
index 13361a7..5a8366c 100644
--- a/test cases/linuxlike/1 pkg-config/meson.build
+++ b/test cases/linuxlike/1 pkg-config/meson.build
@@ -2,11 +2,16 @@ project('external dependency', 'c')
# Zlib is probably on all dev machines.
-dep = dependency('zlib', version : '>=1.2.8')
-exe = executable('zlibprog', 'prog.c', dependencies : dep)
+dep = dependency('zlib', version : '>=1.2')
+exe = executable('zlibprog', 'prog-checkver.c',
+ dependencies : dep,
+ c_args : '-DFOUND_ZLIB="' + dep.version() + '"')
-assert(dep.version().version_compare('>=1.2.8'), 'Pkg-config version numbers exposed incorrectly.')
+assert(dep.version().version_compare('>=1.2'), 'Pkg-config version numbers exposed incorrectly.')
+# Check that the version exposed by zlib internally is the same as the one we
+# retrieve from the pkg-config file. This assumes that the packager didn't mess
+# up, but we can be reasonably sure of that.
test('zlibtest', exe)
zprefix = dep.get_pkgconfig_variable('prefix') # Always set but we can't be sure what the value is.
diff --git a/test cases/linuxlike/1 pkg-config/prog-checkver.c b/test cases/linuxlike/1 pkg-config/prog-checkver.c
new file mode 100644
index 0000000..16b7170
--- /dev/null
+++ b/test cases/linuxlike/1 pkg-config/prog-checkver.c
@@ -0,0 +1,15 @@
+#include <zlib.h>
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char **argv) {
+ void * something = deflate;
+ if(strcmp(ZLIB_VERSION, FOUND_ZLIB) != 0) {
+ printf("Meson found '%s' but zlib is '%s'\n", FOUND_ZLIB, ZLIB_VERSION);
+ return 2;
+ }
+ if(something != 0)
+ return 0;
+ printf("Couldn't find 'deflate'\n");
+ return 1;
+}
diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build
index 1de87c8..7f29564 100644
--- a/test cases/linuxlike/5 dependency versions/meson.build
+++ b/test cases/linuxlike/5 dependency versions/meson.build
@@ -10,6 +10,17 @@ assert(zlib.type_name() == 'pkgconfig', 'zlib should be of type "pkgconfig" not
zlibver = dependency('zlib', version : '<1.0', required : false)
assert(zlibver.found() == false, 'zlib <1.0 should not be found!')
+# Find external dependencies with various version restrictions
+dependency('zlib', version : '>=1.0')
+dependency('zlib', version : '<=9999')
+dependency('zlib', version : '=' + zlib.version())
+
+# Find external dependencies with multiple version restrictions
+dependency('zlib', version : ['>=1.0', '<=9999'])
+if dependency('zlib', version : ['<=1.0', '>=9999', '=' + zlib.version()], required : false).found()
+ error('zlib <=1.0 >=9999 should not have been found')
+endif
+
# Test https://github.com/mesonbuild/meson/pull/610
dependency('somebrokenlib', version : '>=2.0', required : false)
dependency('somebrokenlib', version : '>=1.0', required : false)
diff --git a/test cases/rewrite/1 basic/added.txt b/test cases/rewrite/1 basic/added.txt
new file mode 100644
index 0000000..657dd42
--- /dev/null
+++ b/test cases/rewrite/1 basic/added.txt
@@ -0,0 +1,5 @@
+project('rewritetest', 'c')
+
+sources = ['trivial.c']
+
+exe = executable('trivialprog', 'notthere.c', sources)
diff --git a/test cases/rewrite/1 basic/meson.build b/test cases/rewrite/1 basic/meson.build
new file mode 100644
index 0000000..a0485d0
--- /dev/null
+++ b/test cases/rewrite/1 basic/meson.build
@@ -0,0 +1,5 @@
+project('rewritetest', 'c')
+
+sources = ['trivial.c', 'notthere.c']
+
+exe = executable('trivialprog', sources)
diff --git a/test cases/rewrite/1 basic/removed.txt b/test cases/rewrite/1 basic/removed.txt
new file mode 100644
index 0000000..5519214
--- /dev/null
+++ b/test cases/rewrite/1 basic/removed.txt
@@ -0,0 +1,5 @@
+project('rewritetest', 'c')
+
+sources = ['trivial.c']
+
+exe = executable('trivialprog', sources)
diff --git a/test cases/rewrite/2 subdirs/meson.build b/test cases/rewrite/2 subdirs/meson.build
new file mode 100644
index 0000000..79b7ad7
--- /dev/null
+++ b/test cases/rewrite/2 subdirs/meson.build
@@ -0,0 +1,5 @@
+project('subdir rewrite', 'c')
+
+subdir('sub1')
+subdir('sub2')
+
diff --git a/test cases/rewrite/2 subdirs/sub1/after.txt b/test cases/rewrite/2 subdirs/sub1/after.txt
new file mode 100644
index 0000000..53ceaff
--- /dev/null
+++ b/test cases/rewrite/2 subdirs/sub1/after.txt
@@ -0,0 +1 @@
+srcs = ['first.c']
diff --git a/test cases/rewrite/2 subdirs/sub1/meson.build b/test cases/rewrite/2 subdirs/sub1/meson.build
new file mode 100644
index 0000000..ca42205
--- /dev/null
+++ b/test cases/rewrite/2 subdirs/sub1/meson.build
@@ -0,0 +1 @@
+srcs = ['first.c', 'second.c']
diff --git a/test cases/rewrite/2 subdirs/sub2/meson.build b/test cases/rewrite/2 subdirs/sub2/meson.build
new file mode 100644
index 0000000..0d92e7f
--- /dev/null
+++ b/test cases/rewrite/2 subdirs/sub2/meson.build
@@ -0,0 +1,2 @@
+executable('something', srcs)
+
diff --git a/test cases/unit/1 soname/CMakeLists.txt b/test cases/unit/1 soname/CMakeLists.txt
new file mode 100644
index 0000000..c4f2e3e
--- /dev/null
+++ b/test cases/unit/1 soname/CMakeLists.txt
@@ -0,0 +1,26 @@
+# This is a CMake version of this test. It behaves slightly differently
+# so in case you ever need to debug this, here it is.
+#
+# The biggest difference is that if SOVERSION is not set, it
+# is set to VERSION. Autotools sets it to the first number
+# of VERSION. That is, for version number 1.2.3 CMake sets
+# soname to 1.2.3 but Autotools sets it to 1.
+
+project(vertest C)
+cmake_minimum_required(VERSION 3.5)
+
+add_library(nover SHARED versioned.c)
+
+add_library(verset SHARED versioned.c)
+set_target_properties(verset PROPERTIES VERSION 4.5.6)
+
+add_library(soverset SHARED versioned.c)
+set_target_properties(soverset PROPERTIES SOVERSION 1.2.3)
+
+add_library(bothset SHARED versioned.c)
+set_target_properties(bothset PROPERTIES SOVERSION 1.2.3)
+set_target_properties(bothset PROPERTIES VERSION 4.5.6)
+
+add_library(settosame SHARED versioned.c)
+set_target_properties(settosame PROPERTIES SOVERSION 7.8.9)
+set_target_properties(settosame PROPERTIES VERSION 7.8.9)
diff --git a/test cases/unit/1 soname/meson.build b/test cases/unit/1 soname/meson.build
new file mode 100644
index 0000000..d956afe
--- /dev/null
+++ b/test cases/unit/1 soname/meson.build
@@ -0,0 +1,18 @@
+project('vertest', 'c')
+
+shared_library('nover', 'versioned.c')
+
+shared_library('verset', 'versioned.c',
+ version : '4.5.6')
+
+shared_library('soverset', 'versioned.c',
+ soversion : '1.2.3')
+
+shared_library('bothset', 'versioned.c',
+ soversion : '1.2.3',
+ version : '4.5.6')
+
+shared_library('settosame', 'versioned.c',
+ soversion : '7.8.9',
+ version : '7.8.9')
+
diff --git a/test cases/unit/1 soname/versioned.c b/test cases/unit/1 soname/versioned.c
new file mode 100644
index 0000000..f48d2b0
--- /dev/null
+++ b/test cases/unit/1 soname/versioned.c
@@ -0,0 +1,3 @@
+int versioned_func() {
+ return 0;
+}
diff --git a/test cases/vala/11 mixed sources/c/foo.c b/test cases/vala/10 mixed sources/c/foo.c
index f3c6fb8..f3c6fb8 100644
--- a/test cases/vala/11 mixed sources/c/foo.c
+++ b/test cases/vala/10 mixed sources/c/foo.c
diff --git a/test cases/vala/11 mixed sources/c/meson.build b/test cases/vala/10 mixed sources/c/meson.build
index ead0575..ead0575 100644
--- a/test cases/vala/11 mixed sources/c/meson.build
+++ b/test cases/vala/10 mixed sources/c/meson.build
diff --git a/test cases/vala/11 mixed sources/c/writec.py b/test cases/vala/10 mixed sources/c/writec.py
index 2cc822b..2cc822b 100644
--- a/test cases/vala/11 mixed sources/c/writec.py
+++ b/test cases/vala/10 mixed sources/c/writec.py
diff --git a/test cases/vala/11 mixed sources/meson.build b/test cases/vala/10 mixed sources/meson.build
index 75b8ecd..75b8ecd 100644
--- a/test cases/vala/11 mixed sources/meson.build
+++ b/test cases/vala/10 mixed sources/meson.build
diff --git a/test cases/vala/11 mixed sources/vala/bar.vala b/test cases/vala/10 mixed sources/vala/bar.vala
index 10dce1e..10dce1e 100644
--- a/test cases/vala/11 mixed sources/vala/bar.vala
+++ b/test cases/vala/10 mixed sources/vala/bar.vala
diff --git a/test cases/vala/12 generated vapi/installed_files.txt b/test cases/vala/11 generated vapi/installed_files.txt
index 5993d01..5993d01 100644
--- a/test cases/vala/12 generated vapi/installed_files.txt
+++ b/test cases/vala/11 generated vapi/installed_files.txt
diff --git a/test cases/vala/12 generated vapi/libbar/bar.c b/test cases/vala/11 generated vapi/libbar/bar.c
index f0f5cb8..f0f5cb8 100644
--- a/test cases/vala/12 generated vapi/libbar/bar.c
+++ b/test cases/vala/11 generated vapi/libbar/bar.c
diff --git a/test cases/vala/12 generated vapi/libbar/bar.h b/test cases/vala/11 generated vapi/libbar/bar.h
index 165b104..165b104 100644
--- a/test cases/vala/12 generated vapi/libbar/bar.h
+++ b/test cases/vala/11 generated vapi/libbar/bar.h
diff --git a/test cases/vala/12 generated vapi/libbar/meson.build b/test cases/vala/11 generated vapi/libbar/meson.build
index 6482504..6482504 100644
--- a/test cases/vala/12 generated vapi/libbar/meson.build
+++ b/test cases/vala/11 generated vapi/libbar/meson.build
diff --git a/test cases/vala/12 generated vapi/libfoo/foo.c b/test cases/vala/11 generated vapi/libfoo/foo.c
index 0413ac5..0413ac5 100644
--- a/test cases/vala/12 generated vapi/libfoo/foo.c
+++ b/test cases/vala/11 generated vapi/libfoo/foo.c
diff --git a/test cases/vala/12 generated vapi/libfoo/foo.h b/test cases/vala/11 generated vapi/libfoo/foo.h
index f09256d..f09256d 100644
--- a/test cases/vala/12 generated vapi/libfoo/foo.h
+++ b/test cases/vala/11 generated vapi/libfoo/foo.h
diff --git a/test cases/vala/12 generated vapi/libfoo/meson.build b/test cases/vala/11 generated vapi/libfoo/meson.build
index 482c8fe..482c8fe 100644
--- a/test cases/vala/12 generated vapi/libfoo/meson.build
+++ b/test cases/vala/11 generated vapi/libfoo/meson.build
diff --git a/test cases/vala/12 generated vapi/main.vala b/test cases/vala/11 generated vapi/main.vala
index 303ab33..303ab33 100644
--- a/test cases/vala/12 generated vapi/main.vala
+++ b/test cases/vala/11 generated vapi/main.vala
diff --git a/test cases/vala/12 generated vapi/meson.build b/test cases/vala/11 generated vapi/meson.build
index 82f0c44..82f0c44 100644
--- a/test cases/vala/12 generated vapi/meson.build
+++ b/test cases/vala/11 generated vapi/meson.build
diff --git a/test cases/vala/13 custom output/foo.vala b/test cases/vala/12 custom output/foo.vala
index e69de29..e69de29 100644
--- a/test cases/vala/13 custom output/foo.vala
+++ b/test cases/vala/12 custom output/foo.vala
diff --git a/test cases/vala/13 custom output/meson.build b/test cases/vala/12 custom output/meson.build
index ef6dbb5..ef6dbb5 100644
--- a/test cases/vala/13 custom output/meson.build
+++ b/test cases/vala/12 custom output/meson.build
diff --git a/test cases/vala/14 find library/meson.build b/test cases/vala/13 find library/meson.build
index 03054d2..03054d2 100644
--- a/test cases/vala/14 find library/meson.build
+++ b/test cases/vala/13 find library/meson.build
diff --git a/test cases/vala/14 find library/test.vala b/test cases/vala/13 find library/test.vala
index b087cfb..b087cfb 100644
--- a/test cases/vala/14 find library/test.vala
+++ b/test cases/vala/13 find library/test.vala