aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--authors.txt1
-rw-r--r--data/macros.meson23
-rw-r--r--mesonbuild/astinterpreter.py241
-rw-r--r--mesonbuild/backend/ninjabackend.py27
-rw-r--r--mesonbuild/backend/vs2010backend.py11
-rw-r--r--mesonbuild/build.py6
-rw-r--r--mesonbuild/compilers.py59
-rw-r--r--mesonbuild/coredata.py12
-rw-r--r--mesonbuild/dependencies.py40
-rw-r--r--mesonbuild/interpreter.py655
-rw-r--r--mesonbuild/interpreterbase.py636
-rw-r--r--mesonbuild/mconf.py1
-rw-r--r--mesonbuild/mesonlib.py29
-rw-r--r--mesonbuild/mesonmain.py5
-rw-r--r--mesonbuild/mintro.py21
-rw-r--r--mesonbuild/modules/gnome.py133
-rw-r--r--mesonbuild/modules/i18n.py67
-rw-r--r--mesonbuild/modules/pkgconfig.py8
-rw-r--r--mesonbuild/mparser.py127
-rw-r--r--mesonbuild/optinterpreter.py2
-rw-r--r--mesonbuild/scripts/gettext.py23
-rwxr-xr-xmesonbuild/scripts/gtkdochelper.py52
-rwxr-xr-xmesonbuild/scripts/meson_install.py4
-rwxr-xr-xmesonbuild/scripts/regen_checker.py2
-rwxr-xr-xmesonbuild/scripts/symbolextractor.py16
-rw-r--r--mesonbuild/scripts/yelphelper.py6
-rw-r--r--mesonbuild/wrap/wrap.py29
-rwxr-xr-xmesonrewriter.py64
-rwxr-xr-xmesontest.py128
-rwxr-xr-xrun_project_tests.py13
-rwxr-xr-xrun_unittests.py204
-rw-r--r--syntax-highlighting/vim/syntax/meson.vim64
-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/failing/38 has function external dependency/meson.build8
-rw-r--r--test cases/failing/38 has function external dependency/mylib.c1
-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
78 files changed, 2054 insertions, 869 deletions
diff --git a/.travis.yml b/.travis.yml
index 8648572..2acb908 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,9 @@
sudo: false
+branches:
+ only:
+ - master
+
os:
- linux
- osx
diff --git a/authors.txt b/authors.txt
index 03e8478..12e944e 100644
--- a/authors.txt
+++ b/authors.txt
@@ -57,3 +57,4 @@ Mark Schulte
Paulo Antonio Alvarez
Olexa Bilaniuk
Daniel Stone
+Marc-Antoine Perennou
diff --git a/data/macros.meson b/data/macros.meson
index 4b91c70..97d8a23 100644
--- a/data/macros.meson
+++ b/data/macros.meson
@@ -10,17 +10,18 @@
export LDFLAGS="%{?__global_ldflags}" \
mkdir -p %{__builddir} \
pushd %{__builddir} \
- %{__meson} \\\
- --buildtype=plain \\\
- --prefix=%{_prefix} \\\
- --libdir=%{_libdir} \\\
- --libexecdir=%{_libexecdir} \\\
- --bindir=%{_bindir} \\\
- --includedir=%{_includedir} \\\
- --datadir=%{_datadir} \\\
- --mandir=%{_mandir} \\\
- --localedir=%{_datadir}/locale \\\
- --sysconfdir=%{_sysconfdir} \\\
+ %{__meson} \\\
+ --buildtype=plain \\\
+ --prefix=%{_prefix} \\\
+ --libdir=%{_libdir} \\\
+ --libexecdir=%{_libexecdir} \\\
+ --bindir=%{_bindir} \\\
+ --includedir=%{_includedir} \\\
+ --datadir=%{_datadir} \\\
+ --mandir=%{_mandir} \\\
+ --localedir=%{_datadir}/locale \\\
+ --sysconfdir=%{_sysconfdir} \\\
+ --localstatedir=%{_localstatedir} \\\
$OLDPWD/%{__sourcedir} \
popd
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 4792e96..e11491f 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -664,7 +664,9 @@ int dummy;
if outdir is None:
outdir = os.path.join(incroot, h.get_install_subdir())
for f in h.get_sources():
- assert(isinstance(f, File))
+ if not isinstance(f, File):
+ msg = 'Invalid header type {!r} can\'t be installed'
+ raise MesonException(msg.format(f))
abspath = f.absolute_path(srcdir, builddir)
i = [abspath, outdir]
d.headers.append(i)
@@ -716,7 +718,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'):
@@ -729,7 +731,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.')
@@ -1018,9 +1020,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')
@@ -2010,14 +2014,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/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index eb62bd1..d043455 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -15,17 +15,16 @@
import os, sys
import pickle
import re
+import xml.dom.minidom
+import xml.etree.ElementTree as ET
-from mesonbuild import compilers
-from mesonbuild.build import BuildTarget
-from mesonbuild.mesonlib import File
from . import backends
from .. import build
from .. import dependencies
from .. import mlog
-import xml.etree.ElementTree as ET
-import xml.dom.minidom
-from ..mesonlib import MesonException
+from .. import compilers
+from ..build import BuildTarget
+from ..mesonlib import MesonException, File
from ..environment import Environment
def split_o_flags_args(args):
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index b5ca5aa..79759ee 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 b019e5f..0f6250f 100644
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -707,8 +707,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:
@@ -719,9 +739,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
@@ -729,6 +750,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
@@ -742,17 +768,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):
@@ -801,7 +834,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):
@@ -850,7 +882,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):
@@ -986,14 +1017,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:
@@ -1077,8 +1108,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/coredata.py b/mesonbuild/coredata.py
index 29ea1bf..1a5abcc 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -212,15 +212,17 @@ builtin_options = {
'datadir' : [ UserStringOption, 'Data file directory.', 'share' ],
'mandir' : [ UserStringOption, 'Manual page directory.', 'share/man' ],
'localedir' : [ UserStringOption, 'Locale data directory.', 'share/locale' ],
- # Sysconfdir is a bit special. It defaults to ${prefix}/etc but nobody
- # uses that. Instead they always set it manually to /etc. This default
- # value is thus pointless and not really used but we set it to this
+ # sysconfdir and localstatedir are a bit special. These defaults to ${prefix}/etc and
+ # ${prefix}/var but nobody uses that. Instead they always set it manually to /etc and /var.
+ # This default values is thus pointless and not really used but we set it to this
# for consistency with other systems.
#
- # Projects installing to sysconfdir probably want to set the following in project():
+ # Projects installing to sysconfdir and/or localstatedir probably want
+ # to set the following in project():
#
- # default_options : ['sysconfdir=/etc']
+ # default_options : ['sysconfdir=/etc', 'localstatedir=/var']
'sysconfdir' : [ UserStringOption, 'Sysconf data directory.', 'etc' ],
+ 'localstatedir' : [ UserStringOption, 'Localstate data directory.', 'var' ],
'werror' : [ UserBooleanOption, 'Treat warnings as errors.', False ],
'warning_level' : [ UserComboOption, 'Compiler warning level to use.', [ '1', '2', '3' ], '1'],
'layout' : [ UserComboOption, 'Build directory layout.', ['mirror', 'flat' ], 'mirror' ],
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py
index 4e87e4e..b76c3cb 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
@@ -123,7 +123,11 @@ class PkgConfigDependency(Dependency):
pkgbin = environment.cross_info.config["binaries"]['pkgconfig']
self.type_string = 'Cross'
else:
- pkgbin = 'pkg-config'
+ evar = 'PKG_CONFIG'
+ if evar in os.environ:
+ pkgbin = os.environ[evar].strip()
+ else:
+ pkgbin = 'pkg-config'
self.type_string = 'Native'
mlog.debug('Determining dependency %s with pkg-config executable %s.' % (name, pkgbin))
@@ -135,22 +139,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:
@@ -224,12 +233,17 @@ class PkgConfigDependency(Dependency):
def check_pkgconfig(self):
try:
- p = subprocess.Popen(['pkg-config', '--version'], stdout=subprocess.PIPE,
+ evar = 'PKG_CONFIG'
+ if evar in os.environ:
+ pkgbin = os.environ[evar].strip()
+ else:
+ pkgbin = 'pkg-config'
+ p = subprocess.Popen([pkgbin, '--version'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out = p.communicate()[0]
if p.returncode == 0:
if not self.silent:
- mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')),
+ mlog.log('Found pkg-config:', mlog.bold(shutil.which(pkgbin)),
'(%s)' % out.decode().strip())
PkgConfigDependency.pkgconfig_found = True
return
@@ -301,7 +315,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 502caff..3d4f092 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -22,58 +22,18 @@ from . import optinterpreter
from . import compilers
from .wrap import wrap
from . import mesonlib
+from .dependencies import InternalDependency, Dependency
+from .interpreterbase import InterpreterBase
+from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs
+from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode
+from .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 +43,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):
@@ -396,6 +344,10 @@ class GeneratedListHolder(InterpreterObject):
else:
self.held_object = arg1
+ def __repr__(self):
+ r = '<{}: {!r}>'
+ return r.format(self.__class__.__name__, self.held_object.get_outputs())
+
def add_file(self, a):
self.held_object.add_file(a)
@@ -550,6 +502,11 @@ class BuildTargetHolder(InterpreterObject):
'private_dir_include' : self.private_dir_include_method,
})
+ def __repr__(self):
+ r = '<{} {}: {}>'
+ h = self.held_object
+ return r.format(self.__class__.__name__, h.get_id(), h.filename)
+
def is_cross(self):
return self.held_object.is_cross()
@@ -602,6 +559,11 @@ class CustomTargetHolder(InterpreterObject):
self.methods.update({'full_path' : self.full_path_method,
})
+ def __repr__(self):
+ r = '<{} {}: {}>'
+ h = self.held_object
+ return r.format(self.__class__.__name__, h.get_id(), h.command)
+
def full_path_method(self, args, kwargs):
return self.interpreter.backend.get_target_filename_abs(self.held_object)
@@ -609,6 +571,11 @@ class RunTargetHolder(InterpreterObject):
def __init__(self, name, command, args, dependencies, subdir):
self.held_object = build.RunTarget(name, command, args, dependencies, subdir)
+ def __repr__(self):
+ r = '<{} {}: {}>'
+ h = self.held_object
+ return r.format(self.__class__.__name__, h.get_id(), h.command)
+
class Test(InterpreterObject):
def __init__(self, name, suite, exe, is_parallel, cmd_args, env, should_fail, timeout, workdir):
InterpreterObject.__init__(self)
@@ -687,10 +654,8 @@ class CompilerHolder(InterpreterObject):
args += mesonlib.stringlistify(kwargs.get('args', []))
return args
- def determine_dependencies(self, kwargs, allowed_dep_types=None):
+ def determine_dependencies(self, kwargs):
deps = kwargs.get('dependencies', None)
- if allowed_dep_types is None:
- allowed_dep_types = (dependencies.Dependency, dependencies.ExternalLibrary)
if deps is not None:
if not isinstance(deps, list):
deps = [deps]
@@ -700,8 +665,8 @@ class CompilerHolder(InterpreterObject):
d = d.held_object
except Exception:
pass
- if not isinstance(d, allowed_dep_types):
- raise InterpreterException('Dependencies must be external deps')
+ if isinstance(d, InternalDependency) or not isinstance(d, Dependency):
+ raise InterpreterException('Dependencies must be external dependencies')
final_deps.append(d)
deps = final_deps
return deps
@@ -760,7 +725,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(prefix, str):
raise InterpreterException('Prefix argument of has_member must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
had = self.compiler.has_members(typename, [membername], prefix,
self.environment, extra_args, deps)
if had:
@@ -779,7 +744,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(prefix, str):
raise InterpreterException('Prefix argument of has_members must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
had = self.compiler.has_members(typename, membernames, prefix,
self.environment, extra_args, deps)
if had:
@@ -836,7 +801,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(prefix, str):
raise InterpreterException('Prefix argument of sizeof must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
esize = self.compiler.sizeof(element, prefix, self.environment, extra_args, deps)
mlog.log('Checking for size of "%s": %d' % (element, esize))
return esize
@@ -854,7 +819,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(testname, str):
raise InterpreterException('Testname argument must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
result = self.compiler.compiles(code, self.environment, extra_args, deps)
if len(testname) > 0:
if result:
@@ -896,7 +861,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(prefix, str):
raise InterpreterException('Prefix argument of has_header must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
haz = self.compiler.has_header(hname, prefix, self.environment, extra_args, deps)
if haz:
h = mlog.green('YES')
@@ -915,7 +880,7 @@ class CompilerHolder(InterpreterObject):
if not isinstance(prefix, str):
raise InterpreterException('Prefix argument of has_header_symbol must be a string.')
extra_args = self.determine_args(kwargs)
- deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,))
+ deps = self.determine_dependencies(kwargs)
haz = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment, extra_args, deps)
if haz:
h = mlog.green('YES')
@@ -1147,16 +1112,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):
@@ -1164,22 +1128,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
@@ -1204,7 +1155,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,
@@ -1250,14 +1201,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
@@ -1304,16 +1248,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
@@ -1330,71 +1264,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):
@@ -1455,63 +1324,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):
@@ -1595,7 +1407,7 @@ class Interpreter():
raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted))
self.active_projectname = current_active
mlog.log('\nSubproject', mlog.bold(dirname), 'finished.')
- self.build.subprojects[dirname] = True
+ self.build.subprojects[dirname] = subi.project_version
self.subprojects.update(subi.subprojects)
self.subprojects[dirname] = SubprojectHolder(subi)
self.build_def_files += subi.build_def_files
@@ -1669,6 +1481,8 @@ class Interpreter():
raise InvalidArguments('Not enough arguments to project(). Needs at least the project name and one language')
self.active_projectname = args[0]
self.project_version = kwargs.get('version', 'undefined')
+ if self.build.project_version is None:
+ self.build.project_version = self.project_version
proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown'))
self.build.dep_manifest[args[0]] = {'version': self.project_version,
'license': proj_license}
@@ -1871,7 +1685,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
@@ -1933,8 +1748,9 @@ requirements use the version keyword argument instead.''')
raise
# If the subproject execution failed in a non-fatal way, don't raise an
# exception; let the caller handle things.
- except:
- mlog.log('Also couldn\'t find a fallback subproject in',
+ except Exception as e:
+ mlog.warning(e)
+ mlog.log('Couldn\'t find a fallback subproject in',
mlog.bold(os.path.join(self.subproject_dir, dirname)),
'for the dependency', mlog.bold(name))
return None
@@ -2204,7 +2020,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
@@ -2387,21 +2203,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 = []
@@ -2500,145 +2304,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)
@@ -2651,32 +2316,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):
@@ -2688,20 +2327,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.')
@@ -2718,183 +2343,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/mconf.py b/mesonbuild/mconf.py
index 4ef8d92..6061d48 100644
--- a/mesonbuild/mconf.py
+++ b/mesonbuild/mconf.py
@@ -174,6 +174,7 @@ class Conf:
'mandir',
'localedir',
'sysconfdir',
+ 'localstatedir',
]:
parr.append([key, coredata.get_builtin_option_description(key),
self.coredata.get_builtin_option(key), coredata.get_builtin_option_choices(key)])
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..4f31496 100644
--- a/mesonbuild/mesonmain.py
+++ b/mesonbuild/mesonmain.py
@@ -46,6 +46,7 @@ add_builtin_argument('datadir')
add_builtin_argument('mandir')
add_builtin_argument('localedir')
add_builtin_argument('sysconfdir')
+add_builtin_argument('localstatedir')
add_builtin_argument('backend')
add_builtin_argument('buildtype')
add_builtin_argument('strip', action='store_true')
@@ -115,7 +116,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..f902d3d 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -39,6 +39,8 @@ parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', defa
help='List all benchmarks.')
parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False,
help='list external dependencies.')
+parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False,
+ help='information about projects.')
parser.add_argument('args', nargs='+')
def determine_installed_path(target, installdata):
@@ -168,7 +170,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
@@ -176,6 +181,18 @@ def list_tests(testdata):
result.append(to)
print(json.dumps(result))
+def list_projinfo(builddata):
+ result = {}
+ result['name'] = builddata.project_name
+ result['version'] = builddata.project_version
+ subprojects = []
+ for k, v in builddata.subprojects.items():
+ c = {'name' : k,
+ 'version' : v}
+ subprojects.append(c)
+ result['subprojects'] = subprojects
+ print(json.dumps(result))
+
def run(args):
options = parser.parse_args(args)
if len(options.args) > 1:
@@ -214,6 +231,8 @@ def run(args):
list_tests(benchmarkdata)
elif options.dependencies:
list_deps(coredata)
+ elif options.projectinfo:
+ list_projinfo(builddata)
else:
print('No command specified')
return 1
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 7c4fb1d..b915e96 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,20 +363,23 @@ 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
if not isinstance(girtarget, (build.Executable, build.SharedLibrary)):
raise MesonException('Gir target must be an executable or shared library')
try:
- pkgstr = subprocess.check_output(['pkg-config', '--cflags', 'gobject-introspection-1.0'])
+ gir_dep = dependencies.PkgConfigDependency(
+ 'gobject-introspection-1.0', state.environment, {'native': True})
+ pkgargs = gir_dep.get_compile_args()
except Exception:
global girwarning_printed
if not girwarning_printed:
mlog.warning('gobject-introspection dependency was not found, disabling gir generation.')
girwarning_printed = True
return []
- pkgargs = pkgstr.decode().strip().split()
ns = kwargs.pop('namespace')
nsversion = kwargs.pop('nsversion')
libsources = kwargs.pop('sources')
@@ -329,7 +387,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 +542,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 +564,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'
@@ -594,25 +654,39 @@ can not be used with the current version of glib-compiled-resources, due to
if main_file != '':
raise MesonException('You can only specify main_xml or main_sgml, not both.')
main_file = main_xml
- src_dir = kwargs['src_dir']
targetname = modulename + '-doc'
command = [state.environment.get_build_command(), '--internal', 'gtkdoc']
- if hasattr(src_dir, 'held_object'):
- src_dir= src_dir.held_object
- if not isinstance(src_dir, build.IncludeDirs):
- raise MesonException('Invalid keyword argument for src_dir.')
- incdirs = src_dir.get_incdirs()
- if len(incdirs) != 1:
- raise MesonException('Argument src_dir has more than one directory specified.')
- header_dir = os.path.join(state.environment.get_source_dir(), src_dir.get_curdir(), incdirs[0])
- else:
- header_dir = os.path.normpath(os.path.join(state.subdir, src_dir))
+
+ namespace = kwargs.get('namespace', '')
+ mode = kwargs.get('mode', 'auto')
+ VALID_MODES = ('xml', 'sgml', 'none', 'auto')
+ if not mode in VALID_MODES:
+ raise MesonException('gtkdoc: Mode {} is not a valid mode: {}'.format(mode, VALID_MODES))
+
+ src_dirs = kwargs['src_dir']
+ if not isinstance(src_dirs, list):
+ src_dirs = [src_dirs]
+ header_dirs = []
+ for src_dir in src_dirs:
+ if hasattr(src_dir, 'held_object'):
+ src_dir = src_dir.held_object
+ if not isinstance(src_dir, build.IncludeDirs):
+ raise MesonException('Invalid keyword argument for src_dir.')
+ for inc_dir in src_dir.get_incdirs():
+ header_dirs.append(os.path.join(state.environment.get_source_dir(),
+ src_dir.get_curdir(), inc_dir))
+ else:
+ header_dirs.append(os.path.normpath(os.path.join(state.subdir, src_dir)))
+
args = ['--sourcedir=' + state.environment.get_source_dir(),
'--builddir=' + state.environment.get_build_dir(),
'--subdir=' + state.subdir,
- '--headerdir=' + header_dir,
+ '--headerdirs=' + '@@'.join(header_dirs),
'--mainfile=' + main_file,
- '--modulename=' + modulename]
+ '--modulename=' + modulename,
+ '--mode=' + mode]
+ if namespace:
+ args.append('--namespace=' + namespace)
args += self._unpack_args('--htmlargs=', 'html_args', kwargs)
args += self._unpack_args('--scanargs=', 'scan_args', kwargs)
args += self._unpack_args('--scanobjsargs=', 'scanobjs_args', kwargs)
@@ -620,6 +694,7 @@ can not be used with the current version of glib-compiled-resources, due to
args += self._unpack_args('--fixxrefargs=', 'fixxref_args', kwargs)
args += self._unpack_args('--html-assets=', 'html_assets', kwargs, state)
args += self._unpack_args('--content-files=', 'content_files', kwargs, state)
+ args += self._unpack_args('--expand-content-files=', 'expand_content_files', kwargs, state)
args += self._unpack_args('--ignore-headers=', 'ignore_headers', kwargs)
args += self._unpack_args('--installdir=', 'install_dir', kwargs, state)
args += self._get_build_args(kwargs, state)
@@ -684,7 +759,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 +771,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 +817,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 +898,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 +1025,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..29db960 100644
--- a/mesonbuild/modules/i18n.py
+++ b/mesonbuild/modules/i18n.py
@@ -12,23 +12,72 @@
# 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)
+
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', []))
- 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)
+ lang_arg = '--langs=' + '@@'.join(languages) if languages else None
datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None
extra_args = '--extra-args=' + '@@'.join(extra_args) if extra_args else None
@@ -39,10 +88,14 @@ class I18nModule:
potargs.append(extra_args)
pottarget = build.RunTarget(packagename + '-pot', sys.executable, potargs, [], state.subdir)
- gmoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'gen_gmo', lang_arg]
+ gmoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'gen_gmo']
+ if lang_arg:
+ gmoargs.append(lang_arg)
gmotarget = build.RunTarget(packagename + '-gmo', sys.executable, gmoargs, [], state.subdir)
- updatepoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'update_po', pkg_arg, lang_arg]
+ updatepoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'update_po', pkg_arg]
+ if lang_arg:
+ updatepoargs.append(lang_arg)
if datadirs:
updatepoargs.append(datadirs)
if extra_args:
@@ -53,7 +106,9 @@ class I18nModule:
'--internal', 'gettext', 'install',
'--subdir=' + state.subdir,
'--localedir=' + state.environment.coredata.get_builtin_option('localedir'),
- pkg_arg, lang_arg]
+ pkg_arg]
+ if lang_arg:
+ installcmd.append(lang_arg)
iscript = build.InstallScript(installcmd)
return [pottarget, gmotarget, iscript, updatepotarget]
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
index 9f50b0e..f74f9e9 100644
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -49,13 +49,13 @@ 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)
if len(url) > 0:
ofile.write('URL: %s\n' % url)
- if len(version) > 0:
- ofile.write('Version: %s\n' % version)
+ ofile.write('Version: %s\n' % version)
if len(pub_reqs) > 0:
ofile.write('Requires: {}\n'.format(' '.join(pub_reqs)))
if len(priv_reqs) > 0:
@@ -111,9 +111,9 @@ class PkgConfigModule:
libs = self.process_libs(kwargs.get('libraries', []))
priv_libs = self.process_libs(kwargs.get('libraries_private', []))
subdirs = mesonlib.stringlistify(kwargs.get('subdirs', ['.']))
- version = kwargs.get('version', '')
+ version = kwargs.get('version', None)
if not isinstance(version, str):
- raise mesonlib.MesonException('Version must be a string.')
+ raise mesonlib.MesonException('Version must be specified.')
name = kwargs.get('name', None)
if not isinstance(name, str):
raise mesonlib.MesonException('Name not specified.')
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/mesonbuild/scripts/gettext.py b/mesonbuild/scripts/gettext.py
index 16096b8..8baf323 100644
--- a/mesonbuild/scripts/gettext.py
+++ b/mesonbuild/scripts/gettext.py
@@ -16,7 +16,7 @@ import os
import shutil
import argparse
import subprocess
-from mesonbuild.scripts import destdir_join
+from . import destdir_join
parser = argparse.ArgumentParser()
parser.add_argument('command')
@@ -27,6 +27,22 @@ parser.add_argument('--localedir', default='')
parser.add_argument('--subdir', default='')
parser.add_argument('--extra-args', default='')
+def read_linguas(src_sub):
+ # Syntax of this file is documented here:
+ # https://www.gnu.org/software/gettext/manual/html_node/po_002fLINGUAS.html
+ linguas = os.path.join(src_sub, 'LINGUAS')
+ try:
+ langs = []
+ with open(linguas) as f:
+ for line in f:
+ line = line.strip()
+ if line and not line.startswith('#'):
+ langs += line.split()
+ return langs
+ except (FileNotFoundError, PermissionError):
+ print('Could not find file LINGUAS in {}'.format(src_sub))
+ return []
+
def run_potgen(src_sub, pkgname, datadirs, args):
listfile = os.path.join(src_sub, 'POTFILES')
if not os.path.exists(listfile):
@@ -71,12 +87,15 @@ def do_install(src_sub, bld_sub, dest, pkgname, langs):
def run(args):
options = parser.parse_args(args)
subcmd = options.command
- langs = options.langs.split('@@')
+ langs = options.langs.split('@@') if options.langs else None
extra_args = options.extra_args.split('@@')
subdir = os.environ.get('MESON_SUBDIR', options.subdir)
src_sub = os.path.join(os.environ['MESON_SOURCE_ROOT'], subdir)
bld_sub = os.path.join(os.environ['MESON_BUILD_ROOT'], subdir)
+ if not langs:
+ langs = read_linguas(src_sub)
+
if subcmd == 'pot':
return run_potgen(src_sub, options.pkgname, options.datadirs, extra_args)
elif subcmd == 'gen_gmo':
diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py
index e34b541..14de486 100755
--- a/mesonbuild/scripts/gtkdochelper.py
+++ b/mesonbuild/scripts/gtkdochelper.py
@@ -17,15 +17,15 @@ import sys, os
import subprocess
import shutil
import argparse
-from mesonbuild.mesonlib import MesonException
-from mesonbuild.scripts import destdir_join
+from ..mesonlib import MesonException
+from . import destdir_join
parser = argparse.ArgumentParser()
parser.add_argument('--sourcedir', dest='sourcedir')
parser.add_argument('--builddir', dest='builddir')
parser.add_argument('--subdir', dest='subdir')
-parser.add_argument('--headerdir', dest='headerdir')
+parser.add_argument('--headerdirs', dest='headerdirs')
parser.add_argument('--mainfile', dest='mainfile')
parser.add_argument('--modulename', dest='modulename')
parser.add_argument('--htmlargs', dest='htmlargs', default='')
@@ -38,8 +38,11 @@ parser.add_argument('--cc', dest='cc', default='')
parser.add_argument('--ldflags', dest='ldflags', default='')
parser.add_argument('--cflags', dest='cflags', default='')
parser.add_argument('--content-files', dest='content_files', default='')
+parser.add_argument('--expand-content-files', dest='expand_content_files', default='')
parser.add_argument('--html-assets', dest='html_assets', default='')
parser.add_argument('--ignore-headers', dest='ignore_headers', default='')
+parser.add_argument('--namespace', dest='namespace', default='')
+parser.add_argument('--mode', dest='mode', default='')
parser.add_argument('--installdir', dest='install_dir')
def gtkdoc_run_check(cmd, cwd):
@@ -54,13 +57,14 @@ def gtkdoc_run_check(cmd, cwd):
err_msg.append(stdo.decode(errors='ignore'))
raise MesonException('\n'.join(err_msg))
-def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir,
+def build_gtkdoc(source_root, build_root, doc_subdir, src_subdirs,
main_file, module, html_args, scan_args, fixxref_args,
gobject_typesfile, scanobjs_args, ld, cc, ldflags, cflags,
- html_assets, content_files, ignore_headers):
+ html_assets, content_files, ignore_headers, namespace,
+ expand_content_files, mode):
print("Building documentation for %s" % module)
- abs_src = os.path.join(source_root, src_subdir)
+ src_dir_args = ['--source-dir=' + os.path.join(source_root, src_dir) for src_dir in src_subdirs]
doc_src = os.path.join(source_root, doc_subdir)
abs_out = os.path.join(build_root, doc_subdir)
htmldir = os.path.join(abs_out, 'html')
@@ -90,7 +94,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir,
f_abs = os.path.join(doc_src, f)
shutil.copyfile(f_abs, os.path.join(htmldir, os.path.basename(f_abs)))
- scan_cmd = ['gtkdoc-scan', '--module=' + module, '--source-dir=' + abs_src]
+ scan_cmd = ['gtkdoc-scan', '--module=' + module] + src_dir_args
if ignore_headers:
scan_cmd.append('--ignore-headers=' + ' '.join(ignore_headers))
# Add user-specified arguments
@@ -105,16 +109,29 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir,
# Make docbook files
- if main_file.endswith('sgml'):
- modeflag = '--sgml-mode'
- else:
+ if mode == 'auto':
+ # Guessing is probably a poor idea but these keeps compat
+ # with previous behavior
+ if main_file.endswith('sgml'):
+ modeflag = '--sgml-mode'
+ else:
+ modeflag = '--xml-mode'
+ elif mode == 'xml':
modeflag = '--xml-mode'
+ elif mode == 'sgml':
+ modeflag = '--sgml-mode'
+ else: # none
+ modeflag = None
+
mkdb_cmd = ['gtkdoc-mkdb',
'--module=' + module,
'--output-format=xml',
- '--expand-content-files=',
- modeflag,
- '--source-dir=' + abs_src]
+ '--expand-content-files=' + ' '.join(expand_content_files),
+ ] + src_dir_args
+ if namespace:
+ mkdb_cmd.append('--name-space=' + namespace)
+ if modeflag:
+ mkdb_cmd.append(modeflag)
if len(main_file) > 0:
# Yes, this is the flag even if the file is in xml.
mkdb_cmd.append('--main-sgml-file=' + main_file)
@@ -122,7 +139,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir,
# Make HTML documentation
mkhtml_cmd = ['gtkdoc-mkhtml',
- '--path=' + ':'.join((abs_src, abs_out)),
+ '--path=' + ':'.join((doc_src, abs_out)),
module,
] + html_args
if len(main_file) > 0:
@@ -166,7 +183,7 @@ def run(args):
options.sourcedir,
options.builddir,
options.subdir,
- options.headerdir,
+ options.headerdirs.split('@@'),
options.mainfile,
options.modulename,
htmlargs,
@@ -180,7 +197,10 @@ def run(args):
options.cflags,
options.html_assets.split('@@') if options.html_assets else [],
options.content_files.split('@@') if options.content_files else [],
- options.ignore_headers.split('@@') if options.ignore_headers else [])
+ options.ignore_headers.split('@@') if options.ignore_headers else [],
+ options.namespace,
+ options.expand_content_files.split('@@') if options.expand_content_files else [],
+ options.mode)
if 'MESON_INSTALL_PREFIX' in os.environ:
install_dir = options.install_dir if options.install_dir else options.modulename
diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py
index 5cf02e6..14539e0 100755
--- a/mesonbuild/scripts/meson_install.py
+++ b/mesonbuild/scripts/meson_install.py
@@ -16,8 +16,8 @@
import sys, pickle, os, shutil, subprocess, gzip, platform
from glob import glob
-from mesonbuild.scripts import depfixer
-from mesonbuild.scripts import destdir_join
+from . import depfixer
+from . import destdir_join
install_log_file = None
diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py
index e8e1077..5077970 100755
--- a/mesonbuild/scripts/regen_checker.py
+++ b/mesonbuild/scripts/regen_checker.py
@@ -29,7 +29,7 @@ def need_regen(regeninfo, regen_timestamp):
# We must make sure to recreate it, even if we do not regenerate the solution.
# Otherwise, Visual Studio will always consider the REGEN project out of date.
print("Everything is up-to-date, regeneration of build files is not needed.")
- from mesonbuild.backend.vs2010backend import Vs2010Backend
+ from ..backend.vs2010backend import Vs2010Backend
Vs2010Backend.touch_regen_timestamp(regeninfo.build_dir)
return False
diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py
index c117301..9d28028 100755
--- a/mesonbuild/scripts/symbolextractor.py
+++ b/mesonbuild/scripts/symbolextractor.py
@@ -22,7 +22,7 @@
# This file is basically a reimplementation of
# http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c
-import sys, subprocess
+import os, sys, subprocess
from mesonbuild import mesonlib
import argparse
@@ -49,13 +49,23 @@ def write_if_changed(text, outfilename):
f.write(text)
def linux_syms(libfilename, outfilename):
- pe = subprocess.Popen(['readelf', '-d', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ evar = 'READELF'
+ if evar in os.environ:
+ readelfbin = os.environ[evar].strip()
+ else:
+ readelfbin = 'readelf'
+ evar = 'NM'
+ if evar in os.environ:
+ nmbin = os.environ[evar].strip()
+ else:
+ nmbin = 'nm'
+ pe = subprocess.Popen([readelfbin, '-d', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = pe.communicate()[0].decode()
if pe.returncode != 0:
raise RuntimeError('Readelf does not work')
result = [x for x in output.split('\n') if 'SONAME' in x]
assert(len(result) <= 1)
- pnm = subprocess.Popen(['nm', '--dynamic', '--extern-only', '--defined-only', '--format=posix', libfilename],
+ pnm = subprocess.Popen([nmbin, '--dynamic', '--extern-only', '--defined-only', '--format=posix', libfilename],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = pnm.communicate()[0].decode()
if pnm.returncode != 0:
diff --git a/mesonbuild/scripts/yelphelper.py b/mesonbuild/scripts/yelphelper.py
index 524ef45..4eec425 100644
--- a/mesonbuild/scripts/yelphelper.py
+++ b/mesonbuild/scripts/yelphelper.py
@@ -16,9 +16,9 @@ import sys, os
import subprocess
import shutil
import argparse
-from mesonbuild import mlog
-from mesonbuild.mesonlib import MesonException
-from mesonbuild.scripts import destdir_join
+from .. import mlog
+from ..mesonlib import MesonException
+from . import destdir_join
parser = argparse.ArgumentParser()
parser.add_argument('command')
diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py
index 37d6df7..12536fe 100644
--- a/mesonbuild/wrap/wrap.py
+++ b/mesonbuild/wrap/wrap.py
@@ -95,14 +95,16 @@ class Resolver:
dirname = os.path.join(self.subdir_root, packagename)
try:
if os.listdir(dirname):
- # The directory is there and not empty? Great, use it.
- return packagename
+ if not os.path.isfile(fname) :
+ # The directory is there, not empty and there isn't a wrap file?
+ # Great, use it.
+ return packagename
else:
- mlog.warning('Subproject directory %s is empty, possibly because of an unfinished'
- 'checkout, removing to reclone' % dirname)
- os.rmdir(checkoutdir)
+ mlog.warning('Subproject directory %s is empty, removing to'
+ ' ensure clean download' % dirname)
+ os.rmdir(dirname)
except NotADirectoryError:
- raise RuntimeError('%s is not a directory, can not use as subproject.' % dirname)
+ raise RuntimeError('%s is not a directory, can not use as subproject.' %dirname)
except FileNotFoundError:
pass
@@ -111,6 +113,11 @@ class Resolver:
return None
p = PackageDefinition(fname)
if p.type == 'file':
+ if os.path.isdir(dirname):
+ # project already there? great, use it!
+ # only for the file case because otherwise we prevent git
+ # and hg from updating the subproject.
+ return packagename
if not os.path.isdir(self.cachedir):
os.mkdir(self.cachedir)
self.download(p, packagename)
@@ -130,12 +137,11 @@ class Resolver:
if is_there:
try:
subprocess.check_call(['git', 'rev-parse'])
- is_there = True
except subprocess.CalledProcessError:
raise RuntimeError('%s is not empty but is not a valid '
'git repository, we can not work with it'
- ' as a subproject directory.' % (
- checkoutdir))
+ ' as a subproject directory.'
+ % (checkoutdir))
if revno.lower() == 'head':
# Failure to do pull is not a fatal error,
@@ -163,12 +169,17 @@ class Resolver:
revno = p.get('revision')
is_there = os.path.isdir(checkoutdir)
if is_there:
+ if not os.path.isdir(os.path.join(checkoutdir, '.hg')):
+ raise RuntimeError('Subproject %s is not a valid mercurial repo.'%p.get('directory'))
if revno.lower() == 'tip':
# Failure to do pull is not a fatal error,
# because otherwise you can't develop without
# a working net connection.
subprocess.call(['hg', 'pull'], cwd=checkoutdir)
+ subprocess.call(['hg', 'update', 'tip'],cwd = checkoutdir)
else:
+ # check that the tag/branch/revision we want is available in the
+ # repo, if not, pull and update.
if subprocess.call(['hg', 'checkout', revno], cwd=checkoutdir) != 0:
subprocess.check_call(['hg', 'pull'], cwd=checkoutdir)
subprocess.check_call(['hg', 'checkout', revno],
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..21d029d 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,132 @@ 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:
+ if cmd[0] == 'ccache':
+ cmd = cmd[1:]
+ # 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/syntax-highlighting/vim/syntax/meson.vim b/syntax-highlighting/vim/syntax/meson.vim
index c2653ab..21b7453 100644
--- a/syntax-highlighting/vim/syntax/meson.vim
+++ b/syntax-highlighting/vim/syntax/meson.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Meson
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
-" Last Change: 2015 Feb 23
+" Last Change: 2016 Dec 7
" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
" Neil Schemenauer <nas@meson.ca>
" Dmitry Vasiliev
@@ -62,17 +62,57 @@ syn match mesonNumber "\<\d\+\>"
syn keyword mesonConstant false true
" Built-in functions
-syn keyword mesonBuiltin add_global_arguments add_languages benchmark
-syn keyword mesonBuiltin build_target configuration_data configure_file
-syn keyword mesonBuiltin custom_target declare_dependency dependency
-syn keyword mesonBuiltin error executable find_program find_library
-syn keyword mesonBuiltin files generator get_option get_variable
-syn keyword mesonBuiltin gettext import include_directories install_data
-syn keyword mesonBuiltin install_headers install_man install_subdir
-syn keyword mesonBuiltin is_subproject is_variable jar library message
-syn keyword mesonBuiltin project run_command run_target set_variable
-syn keyword mesonBuiltin shared_library static_library subdir subproject
-syn keyword mesonBuiltin test vcs_tag
+syn keyword mesonBuiltin
+ \ add_global_arguments
+ \ add_global_link_arguments
+ \ add_languages
+ \ add_project_arguments
+ \ add_project_arguments
+ \ add_project_link_arguments
+ \ benchmark
+ \ build_machine
+ \ build_target
+ \ configuration_data
+ \ configure_file
+ \ custom_target
+ \ declare_dependency
+ \ dependency
+ \ environment
+ \ error
+ \ executable
+ \ files
+ \ find_library
+ \ find_program
+ \ generator
+ \ get_option
+ \ get_variable
+ \ gettext
+ \ host_machine
+ \ import
+ \ include_directories
+ \ install_data
+ \ install_headers
+ \ install_man
+ \ install_subdir
+ \ is_subproject
+ \ is_variable
+ \ jar
+ \ join_paths
+ \ library
+ \ meson
+ \ message
+ \ project
+ \ run_command
+ \ run_target
+ \ set_variable
+ \ shared_library
+ \ shared_module
+ \ static_library
+ \ subdir
+ \ subproject
+ \ target_machine
+ \ test
+ \ vcs_tag
if exists("meson_space_error_highlight")
" trailing whitespace
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/failing/38 has function external dependency/meson.build b/test cases/failing/38 has function external dependency/meson.build
new file mode 100644
index 0000000..45a3bc2
--- /dev/null
+++ b/test cases/failing/38 has function external dependency/meson.build
@@ -0,0 +1,8 @@
+project('has function ext dep', 'c')
+
+cc = meson.get_compiler('c')
+
+mylib = shared_library('mylib', 'mylib.c')
+mylib_dep = declare_dependency(link_with : mylib)
+# Only external dependencies can work here
+cc.has_function('malloc', dependencies : mylib_dep)
diff --git a/test cases/failing/38 has function external dependency/mylib.c b/test cases/failing/38 has function external dependency/mylib.c
new file mode 100644
index 0000000..d9fbd34
--- /dev/null
+++ b/test cases/failing/38 has function external dependency/mylib.c
@@ -0,0 +1 @@
+int testfunc(void) { return 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