aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--authors.txt4
-rw-r--r--mesonbuild/backend/backends.py73
-rw-r--r--mesonbuild/backend/ninjabackend.py101
-rw-r--r--mesonbuild/backend/vs2010backend.py2
-rw-r--r--mesonbuild/build.py101
-rw-r--r--mesonbuild/compilers.py26
-rw-r--r--mesonbuild/coredata.py1
-rw-r--r--mesonbuild/dependencies.py112
-rw-r--r--mesonbuild/environment.py310
-rw-r--r--mesonbuild/interpreter.py94
-rw-r--r--mesonbuild/mesonlib.py151
-rw-r--r--mesonbuild/mintro.py21
-rw-r--r--mesonbuild/mlog.py17
-rw-r--r--mesonbuild/modules/gnome.py82
-rw-r--r--mesonbuild/modules/qt4.py6
-rw-r--r--mesonbuild/modules/qt5.py6
-rw-r--r--mesonbuild/modules/rpm.py9
-rw-r--r--mesonbuild/scripts/meson_install.py4
-rw-r--r--mesonbuild/wrap/wrap.py2
-rwxr-xr-xmesontest.py99
-rwxr-xr-xrun_tests.py18
-rwxr-xr-xrun_unittests.py878
-rw-r--r--syntax-highlighting/vim/syntax/meson.vim3
-rw-r--r--test cases/common/105 find program path/meson.build21
-rw-r--r--test cases/common/105 find program path/program.py2
-rw-r--r--test cases/common/107 postconf/postconf.py2
-rw-r--r--test cases/common/108 postconf with args/postconf.py2
-rwxr-xr-xtest cases/common/113 generatorcustom/catter.py2
-rwxr-xr-xtest cases/common/113 generatorcustom/gen.py2
-rwxr-xr-xtest cases/common/118 allgenerate/converter.py2
-rw-r--r--test cases/common/118 allgenerate/meson.build2
-rw-r--r--test cases/common/119 pathjoin/meson.build9
-rw-r--r--test cases/common/129 object only target/meson.build2
-rwxr-xr-xtest cases/common/129 object only target/obj_generator.py2
-rw-r--r--test cases/common/131 custom target directory install/docgen.py2
-rwxr-xr-xtest cases/common/133 configure file in generator/src/gen.py2
-rw-r--r--test cases/common/134 generated llvm ir/copyfile.py2
-rw-r--r--test cases/common/135 generated assembly/copyfile.py2
-rw-r--r--test cases/common/16 configure file/check_file.py6
-rwxr-xr-xtest cases/common/16 configure file/generator.py16
-rw-r--r--test cases/common/16 configure file/installed_files.txt2
-rw-r--r--test cases/common/16 configure file/meson.build18
-rw-r--r--test cases/common/16 configure file/subdir/meson.build19
-rw-r--r--test cases/common/3 static/libfile2.c3
-rw-r--r--test cases/common/3 static/meson.build3
-rw-r--r--test cases/common/3 static/meson_options.txt1
-rwxr-xr-xtest cases/common/48 test args/tester.py2
-rw-r--r--test cases/common/56 custom target/meson.build4
-rwxr-xr-xtest cases/common/56 custom target/my_compiler.py15
-rwxr-xr-xtest cases/common/58 run target/check_exists.py2
-rw-r--r--test cases/common/58 run target/converter.py2
-rwxr-xr-xtest cases/common/58 run target/fakeburner.py2
-rw-r--r--test cases/common/58 run target/meson.build6
-rw-r--r--test cases/common/60 install script/myinstall.py2
-rw-r--r--test cases/common/60 install script/src/myinstall.py2
-rwxr-xr-xtest cases/common/61 custom target source output/generator.py2
-rw-r--r--test cases/common/64 custom header generator/makeheader.py2
-rwxr-xr-xtest cases/common/65 multiple generators/mygen.py2
-rwxr-xr-xtest cases/common/72 build always/version_gen.py2
-rw-r--r--test cases/common/76 configure file in custom target/src/mycompiler.py2
-rwxr-xr-xtest cases/common/77 external test program/mytest.py2
-rwxr-xr-xtest cases/common/78 ctarget dependency/gen1.py2
-rwxr-xr-xtest cases/common/78 ctarget dependency/gen2.py2
-rwxr-xr-xtest cases/common/93 private include/stlib/compiler.py2
-rw-r--r--test cases/common/94 default options/meson.build1
-rw-r--r--test cases/common/95 dep fallback/gensrc.py2
-rw-r--r--test cases/common/95 dep fallback/subprojects/boblib/genbob.py2
-rwxr-xr-xtest cases/common/98 gen extra/srcgen.py2
-rw-r--r--test cases/failing/42 abs subdir/bob/meson.build2
-rw-r--r--test cases/failing/42 abs subdir/meson.build6
-rw-r--r--test cases/failing/42 abspath to srcdir/meson.build3
-rw-r--r--test cases/failing/42 custom target plainname many inputs/1.txt1
-rw-r--r--test cases/failing/42 custom target plainname many inputs/2.txt1
-rw-r--r--test cases/failing/42 custom target plainname many inputs/catfiles.py9
-rw-r--r--test cases/failing/42 custom target plainname many inputs/meson.build8
-rw-r--r--test cases/frameworks/7 gnome/resources-data/meson.build4
-rw-r--r--test cases/frameworks/7 gnome/resources/copyfile.py6
-rw-r--r--test cases/frameworks/7 gnome/resources/meson.build11
-rw-r--r--test cases/linuxlike/5 dependency versions/meson.build2
-rw-r--r--test cases/objc/2 nsstring/meson.build3
-rw-r--r--test cases/unit/2 testsetups/buggy.c4
-rw-r--r--test cases/unit/2 testsetups/meson.build4
-rw-r--r--test cases/unit/5 compiler detection/compiler wrapper.py6
-rw-r--r--test cases/unit/5 compiler detection/meson.build8
-rw-r--r--test cases/unit/5 compiler detection/trivial.c6
-rw-r--r--test cases/unit/5 compiler detection/trivial.cc6
-rw-r--r--test cases/unit/5 compiler detection/trivial.m5
-rw-r--r--test cases/unit/5 compiler detection/trivial.mm9
-rw-r--r--test cases/windows/9 find program/meson.build8
-rw-r--r--test cases/windows/9 find program/test-script-ext.py3
91 files changed, 1670 insertions, 755 deletions
diff --git a/.travis.yml b/.travis.yml
index 2acb908..b3346a1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -40,5 +40,5 @@ script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM jpakkane/mesonci:yakkety > Dockerfile; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi
- - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX ./run_tests.py -- $MESON_ARGS"; fi
- - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX OBJC=$CC OBJCXX=$CXX ./run_tests.py -- $MESON_ARGS"; fi
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) OBJC=$CC OBJCXX=$CXX ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi
diff --git a/authors.txt b/authors.txt
index 2f36256..80c4bbb 100644
--- a/authors.txt
+++ b/authors.txt
@@ -63,3 +63,7 @@ Kseniia Vasilchuk
Philipp Geier
Mike Sinkovsky
Dima Krasner
+Fabio Porcedda
+Rodrigo Lourenço
+Sebastian Stang
+Marc Becker
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 46f8563..26052d3 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -33,10 +33,11 @@ class CleanTrees:
self.trees = trees
class InstallData:
- def __init__(self, source_dir, build_dir, prefix):
+ def __init__(self, source_dir, build_dir, prefix, strip_bin):
self.source_dir = source_dir
self.build_dir = build_dir
self.prefix = prefix
+ self.strip_bin = strip_bin
self.targets = []
self.headers = []
self.man = []
@@ -215,13 +216,13 @@ class Backend:
exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
with open(exe_data, 'wb') as f:
if isinstance(exe, dependencies.ExternalProgram):
- exe_fullpath = exe.fullpath
+ exe_cmd = exe.get_command()
exe_needs_wrapper = False
elif isinstance(exe, (build.BuildTarget, build.CustomTarget)):
- exe_fullpath = [self.get_target_filename_abs(exe)]
+ exe_cmd = [self.get_target_filename_abs(exe)]
exe_needs_wrapper = exe.is_cross
else:
- exe_fullpath = [exe]
+ exe_cmd = [exe]
exe_needs_wrapper = False
is_cross = exe_needs_wrapper and \
self.environment.is_cross_build() and \
@@ -235,7 +236,7 @@ class Backend:
extra_paths = self.determine_windows_extra_paths(exe)
else:
extra_paths = []
- es = ExecutableSerialisation(basename, exe_fullpath, cmd_args, env,
+ es = ExecutableSerialisation(basename, exe_cmd, cmd_args, env,
is_cross, exe_wrapper, workdir,
extra_paths, capture)
pickle.dump(es, f)
@@ -444,9 +445,9 @@ class Backend:
for t in tests:
exe = t.get_exe()
if isinstance(exe, dependencies.ExternalProgram):
- fname = exe.fullpath
+ cmd = exe.get_command()
else:
- fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
+ cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))]
is_cross = self.environment.is_cross_build() and \
self.environment.cross_info.need_cross_compiler() and \
self.environment.cross_info.need_exe_wrapper()
@@ -471,7 +472,7 @@ class Backend:
cmd_args.append(self.get_target_filename(a))
else:
raise MesonException('Bad object in test command.')
- ts = TestSerialisation(t.get_name(), t.suite, fname, is_cross, exe_wrapper,
+ ts = TestSerialisation(t.get_name(), t.suite, cmd, is_cross, exe_wrapper,
t.is_parallel, cmd_args, t.env, t.should_fail,
t.timeout, t.workdir, extra_paths)
arr.append(ts)
@@ -603,19 +604,15 @@ class Backend:
return srcs
def eval_custom_target_command(self, target, absolute_outputs=False):
- # We only want the outputs to be absolute when using the VS backend
- if not absolute_outputs:
- ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output]
- else:
- ofilenames = [os.path.join(self.environment.get_build_dir(), self.get_target_dir(target), i)
- for i in target.output]
- srcs = self.get_custom_target_sources(target)
+ # We want the outputs to be absolute only when using the VS backend
outdir = self.get_target_dir(target)
- # Many external programs fail on empty arguments.
- if outdir == '':
- outdir = '.'
- if target.absolute_paths:
+ if absolute_outputs:
outdir = os.path.join(self.environment.get_build_dir(), outdir)
+ outputs = []
+ for i in target.output:
+ outputs.append(os.path.join(outdir, i))
+ inputs = self.get_custom_target_sources(target)
+ # Evaluate the command list
cmd = []
for i in target.command:
if isinstance(i, build.Executable):
@@ -631,37 +628,10 @@ class Backend:
if target.absolute_paths:
i = os.path.join(self.environment.get_build_dir(), i)
# FIXME: str types are blindly added ignoring 'target.absolute_paths'
+ # because we can't know if they refer to a file or just a string
elif not isinstance(i, str):
err_msg = 'Argument {0} is of unknown type {1}'
raise RuntimeError(err_msg.format(str(i), str(type(i))))
- for (j, src) in enumerate(srcs):
- i = i.replace('@INPUT%d@' % j, src)
- for (j, res) in enumerate(ofilenames):
- i = i.replace('@OUTPUT%d@' % j, res)
- if '@INPUT@' in i:
- msg = 'Custom target {} has @INPUT@ in the command, but'.format(target.name)
- if len(srcs) == 0:
- raise MesonException(msg + ' no input files')
- if i == '@INPUT@':
- cmd += srcs
- continue
- else:
- if len(srcs) > 1:
- raise MesonException(msg + ' more than one input file')
- i = i.replace('@INPUT@', srcs[0])
- elif '@OUTPUT@' in i:
- msg = 'Custom target {} has @OUTPUT@ in the command, but'.format(target.name)
- if len(ofilenames) == 0:
- raise MesonException(msg + ' no output files')
- if i == '@OUTPUT@':
- cmd += ofilenames
- continue
- else:
- if len(ofilenames) > 1:
- raise MesonException(msg + ' more than one output file')
- i = i.replace('@OUTPUT@', ofilenames[0])
- elif '@OUTDIR@' in i:
- i = i.replace('@OUTDIR@', outdir)
elif '@DEPFILE@' in i:
if target.depfile is None:
msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \
@@ -680,10 +650,11 @@ class Backend:
lead_dir = ''
else:
lead_dir = self.environment.get_build_dir()
- i = i.replace(source,
- os.path.join(lead_dir,
- outdir))
+ i = i.replace(source, os.path.join(lead_dir, outdir))
cmd.append(i)
+ # Substitute the rest of the template strings
+ values = mesonlib.get_filenames_templates_dict(inputs, outputs)
+ cmd = mesonlib.substitute_values(cmd, values)
# This should not be necessary but removing it breaks
# building GStreamer on Windows. The underlying issue
# is problems with quoting backslashes on Windows
@@ -703,7 +674,7 @@ class Backend:
#
# https://github.com/mesonbuild/meson/pull/737
cmd = [i.replace('\\', '/') for i in cmd]
- return srcs, ofilenames, cmd
+ return inputs, outputs, cmd
def run_postconf_scripts(self):
env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 8d5d2e0..5e137ca 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -30,9 +30,11 @@ from collections import OrderedDict
if mesonlib.is_windows():
quote_char = '"'
execute_wrapper = 'cmd /c'
+ rmfile_prefix = 'del /f /s /q {} &&'
else:
quote_char = "'"
execute_wrapper = ''
+ rmfile_prefix = 'rm -f {} &&'
def ninja_quote(text):
return text.replace(' ', '$ ').replace(':', '$:')
@@ -208,9 +210,11 @@ int dummy;
# http://clang.llvm.org/docs/JSONCompilationDatabase.html
def generate_compdb(self):
ninja_exe = environment.detect_ninja()
+ ninja_compdb = [ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER', 'c_CROSS_COMPILER',
+ 'cpp_CROSS_COMPILER']
builddir = self.environment.get_build_dir()
try:
- jsondb = subprocess.check_output([ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER'], cwd=builddir)
+ jsondb = subprocess.check_output(ninja_compdb, cwd=builddir)
with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f:
f.write(jsondb)
except Exception:
@@ -590,9 +594,19 @@ int dummy;
def generate_install(self, outfile):
install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
+ if self.environment.is_cross_build():
+ bins = self.environment.cross_info.config['binaries']
+ if 'strip' not in bins:
+ mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
+ strip_bin = None
+ else:
+ strip_bin = mesonlib.stringlistify(bins['strip'])
+ else:
+ strip_bin = self.environment.native_strip_bin
d = InstallData(self.environment.get_source_dir(),
self.environment.get_build_dir(),
- self.environment.get_prefix())
+ self.environment.get_prefix(),
+ strip_bin)
elem = NinjaBuildElement(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY')
elem.add_dep('all')
elem.add_item('DESC', 'Installing files.')
@@ -1232,15 +1246,22 @@ int dummy;
return
rule = 'rule STATIC%s_LINKER\n' % crstr
if mesonlib.is_windows():
- command_templ = ''' command = %s @$out.rsp
+ command_template = ''' command = {executable} @$out.rsp
rspfile = $out.rsp
- rspfile_content = $LINK_ARGS %s $in
+ rspfile_content = $LINK_ARGS {output_args} $in
'''
else:
- command_templ = ' command = %s $LINK_ARGS %s $in\n'
- command = command_templ % (
- ' '.join(static_linker.get_exelist()),
- ' '.join(static_linker.get_output_args('$out')))
+ command_template = ' command = {executable} $LINK_ARGS {output_args} $in\n'
+ cmdlist = []
+ if isinstance(static_linker, compilers.ArLinker):
+ # `ar` has no options to overwrite archives. It always appends,
+ # which is never what we want. Delete an existing library first if
+ # it exists. https://github.com/mesonbuild/meson/issues/1355
+ cmdlist = [execute_wrapper, rmfile_prefix.format('$out')]
+ cmdlist += static_linker.get_exelist()
+ command = command_template.format(
+ executable=' '.join(cmdlist),
+ output_args=' '.join(static_linker.get_output_args('$out')))
description = ' description = Static linking library $out\n\n'
outfile.write(rule)
outfile.write(command)
@@ -1273,16 +1294,17 @@ int dummy;
pass
rule = 'rule %s%s_LINKER\n' % (langname, crstr)
if mesonlib.is_windows():
- command_template = ''' command = %s @$out.rsp
+ command_template = ''' command = {executable} @$out.rsp
rspfile = $out.rsp
- rspfile_content = %s $ARGS %s $in $LINK_ARGS $aliasing
+ rspfile_content = $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing
'''
else:
- command_template = ' command = %s %s $ARGS %s $in $LINK_ARGS $aliasing\n'
- command = command_template % (
- ' '.join(compiler.get_linker_exelist()),
- ' '.join(cross_args),
- ' '.join(compiler.get_linker_output_args('$out')))
+ command_template = ' command = {executable} $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing\n'
+ command = command_template.format(
+ executable=' '.join(compiler.get_linker_exelist()),
+ cross_args=' '.join(cross_args),
+ output_args=' '.join(compiler.get_linker_output_args('$out'))
+ )
description = ' description = Linking target $out'
outfile.write(rule)
outfile.write(command)
@@ -1386,17 +1408,18 @@ rule FORTRAN_DEP_HACK
if getattr(self, 'created_llvm_ir_rule', False):
return
rule = 'rule llvm_ir{}_COMPILER\n'.format('_CROSS' if is_cross else '')
- args = [' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
- ' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)),
- ' '.join(compiler.get_output_args('$out')),
- ' '.join(compiler.get_compile_only_args())]
if mesonlib.is_windows():
- command_template = ' command = {} @$out.rsp\n' \
+ command_template = ' command = {executable} @$out.rsp\n' \
' rspfile = $out.rsp\n' \
- ' rspfile_content = {} $ARGS {} {} $in\n'
+ ' rspfile_content = {cross_args} $ARGS {output_args} {compile_only_args} $in\n'
else:
- command_template = ' command = {} {} $ARGS {} {} $in\n'
- command = command_template.format(*args)
+ command_template = ' command = {executable} {cross_args} $ARGS {output_args} {compile_only_args} $in\n'
+ command = command_template.format(
+ executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
+ cross_args=' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)),
+ output_args=' '.join(compiler.get_output_args('$out')),
+ compile_only_args=' '.join(compiler.get_compile_only_args())
+ )
description = ' description = Compiling LLVM IR object $in.\n'
outfile.write(rule)
outfile.write(command)
@@ -1448,18 +1471,19 @@ rule FORTRAN_DEP_HACK
quoted_depargs.append(d)
cross_args = self.get_cross_info_lang_args(langname, is_cross)
if mesonlib.is_windows():
- command_template = ''' command = %s @$out.rsp
+ command_template = ''' command = {executable} @$out.rsp
rspfile = $out.rsp
- rspfile_content = %s $ARGS %s %s %s $in
+ rspfile_content = {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in
'''
else:
- command_template = ' command = %s %s $ARGS %s %s %s $in\n'
- command = command_template % (
- ' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
- ' '.join(cross_args),
- ' '.join(quoted_depargs),
- ' '.join(compiler.get_output_args('$out')),
- ' '.join(compiler.get_compile_only_args()))
+ command_template = ' command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n'
+ command = command_template.format(
+ executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
+ cross_args=' '.join(cross_args),
+ dep_args=' '.join(quoted_depargs),
+ output_args=' '.join(compiler.get_output_args('$out')),
+ compile_only_args=' '.join(compiler.get_compile_only_args())
+ )
description = ' description = Compiling %s object $out\n' % langname
if compiler.get_id() == 'msvc':
deps = ' deps = msvc\n'
@@ -1497,12 +1521,13 @@ rule FORTRAN_DEP_HACK
output = ''
else:
output = ' '.join(compiler.get_output_args('$out'))
- command = " command = %s %s $ARGS %s %s %s $in\n" % (
- ' '.join(compiler.get_exelist()),
- ' '.join(cross_args),
- ' '.join(quoted_depargs),
- output,
- ' '.join(compiler.get_compile_only_args()))
+ command = " command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n".format(
+ executable=' '.join(compiler.get_exelist()),
+ cross_args=' '.join(cross_args),
+ dep_args=' '.join(quoted_depargs),
+ output_args=output,
+ compile_only_args=' '.join(compiler.get_compile_only_args())
+ )
description = ' description = Precompiling header %s\n' % '$in'
if compiler.get_id() == 'msvc':
deps = ' deps = msvc\n'
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index 666da7d..547889c 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -395,7 +395,7 @@ class Vs2010Backend(backends.Backend):
if isinstance(i, build.BuildTarget):
cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i)))
elif isinstance(i, dependencies.ExternalProgram):
- cmd += i.fullpath
+ cmd += i.get_command()
else:
cmd.append(i)
cmd_templ = '''"%s" ''' * len(cmd)
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 5466431..bdb1dc3 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -16,7 +16,9 @@ from . import environment
from . import dependencies
from . import mlog
import copy, os, re
-from .mesonlib import File, flatten, MesonException, stringlistify, classify_unity_sources
+from .mesonlib import File, MesonException
+from .mesonlib import flatten, stringlistify, classify_unity_sources
+from .mesonlib import get_filenames_templates_dict, substitute_values
from .environment import for_windows, for_darwin
from .compilers import is_object, clike_langs, lang_suffixes
@@ -227,6 +229,10 @@ class EnvironmentVariables:
def __init__(self):
self.envvars = []
+ def __repr__(self):
+ repr_str = "<{0}: {1}>"
+ return repr_str.format(self.__class__.__name__, self.envvars)
+
def get_value(self, name, values, kwargs):
separator = kwargs.get('separator', os.pathsep)
@@ -318,11 +324,16 @@ class BuildTarget(Target):
raise InvalidArguments('Build target %s has no sources.' % name)
self.process_compilers()
self.validate_sources()
+ self.validate_cross_install(environment)
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
+ def validate_cross_install(self, environment):
+ if environment.is_cross_build() and not self.is_cross and self.install:
+ raise InvalidArguments('Tried to install a natively built target in a cross build.')
+
def get_id(self):
# This ID must also be a valid file name on all OSs.
# It should also avoid shell metacharacters for obvious
@@ -1298,6 +1309,29 @@ class CustomTarget(Target):
deps.append(c)
return deps
+ def flatten_command(self, cmd):
+ if not isinstance(cmd, list):
+ cmd = [cmd]
+ final_cmd = []
+ for c in cmd:
+ if hasattr(c, 'held_object'):
+ c = c.held_object
+ if isinstance(c, (str, File)):
+ final_cmd.append(c)
+ elif isinstance(c, dependencies.ExternalProgram):
+ if not c.found():
+ m = 'Tried to use not-found external program {!r} in "command"'
+ raise InvalidArguments(m.format(c.name))
+ final_cmd += c.get_command()
+ elif isinstance(c, (BuildTarget, CustomTarget)):
+ self.dependencies.append(c)
+ final_cmd.append(c)
+ elif isinstance(c, list):
+ final_cmd += self.flatten_command(c)
+ else:
+ raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
+ return final_cmd
+
def process_kwargs(self, kwargs):
super().process_kwargs(kwargs)
self.sources = kwargs.get('input', [])
@@ -1308,11 +1342,25 @@ class CustomTarget(Target):
self.output = kwargs['output']
if not isinstance(self.output, list):
self.output = [self.output]
+ # This will substitute values from the input into output and return it.
+ inputs = get_sources_string_names(self.sources)
+ values = get_filenames_templates_dict(inputs, [])
for i in self.output:
if not(isinstance(i, str)):
raise InvalidArguments('Output argument not a string.')
if '/' in i:
raise InvalidArguments('Output must not contain a path segment.')
+ if '@INPUT@' in i or '@INPUT0@' in i:
+ m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \
+ 'mean @PLAINNAME@ or @BASENAME@?'
+ raise InvalidArguments(m)
+ # We already check this during substitution, but the error message
+ # will be unclear/confusing, so check it here.
+ if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i):
+ m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \
+ "there is more than one input (we can't know which to use)"
+ raise InvalidArguments(m)
+ self.output = substitute_values(self.output, values)
self.capture = kwargs.get('capture', False)
if self.capture and len(self.output) != 1:
raise InvalidArguments('Capturing can only output to a single file.')
@@ -1325,32 +1373,7 @@ class CustomTarget(Target):
if os.path.split(depfile)[1] != depfile:
raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
self.depfile = depfile
- cmd = kwargs['command']
- if not(isinstance(cmd, list)):
- cmd = [cmd]
- final_cmd = []
- for i, c in enumerate(cmd):
- if hasattr(c, 'held_object'):
- c = c.held_object
- if isinstance(c, (str, File)):
- final_cmd.append(c)
- elif isinstance(c, dependencies.ExternalProgram):
- if not c.found():
- raise InvalidArguments('Tried to use not found external program {!r} in a build rule.'.format(c.name))
- final_cmd += c.get_command()
- elif isinstance(c, (BuildTarget, CustomTarget)):
- self.dependencies.append(c)
- final_cmd.append(c)
- elif isinstance(c, list):
- # Hackety hack, only supports one level of flattening. Should really
- # work to arbtrary depth.
- for s in c:
- if not isinstance(s, str):
- raise InvalidArguments('Array as argument %d contains a non-string.' % i)
- final_cmd.append(s)
- else:
- raise InvalidArguments('Argument %s in "command" is invalid.' % i)
- self.command = final_cmd
+ self.command = self.flatten_command(kwargs['command'])
if self.capture:
for c in self.command:
if isinstance(c, str) and '@OUTPUT@' in c:
@@ -1463,6 +1486,11 @@ class Jar(BuildTarget):
def get_java_args(self):
return self.java_args
+ def validate_cross_install(self, environment):
+ # All jar targets are installable.
+ pass
+
+
class ConfigureFile:
def __init__(self, subdir, sourcename, targetname, configuration_data):
@@ -1532,3 +1560,22 @@ class TestSetup:
self.gdb = gdb
self.timeout_multiplier = timeout_multiplier
self.env = env
+
+def get_sources_string_names(sources):
+ '''
+ For the specified list of @sources which can be strings, Files, or targets,
+ get all the output basenames.
+ '''
+ names = []
+ for s in sources:
+ if hasattr(s, 'held_object'):
+ s = s.held_object
+ if isinstance(s, str):
+ names.append(s)
+ elif isinstance(s, (BuildTarget, CustomTarget, GeneratedList)):
+ names += s.get_outputs()
+ elif isinstance(s, File):
+ names.append(s.fname)
+ else:
+ raise AssertionError('Unknown source type: {!r}'.format(s))
+ return names
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py
index 5351111..8c2bb92 100644
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -188,6 +188,11 @@ gnu_color_args = {'auto': ['-fdiagnostics-color=auto'],
'never': ['-fdiagnostics-color=never'],
}
+clang_color_args = {'auto': ['-Xclang', '-fcolor-diagnostics'],
+ 'always': ['-Xclang', '-fcolor-diagnostics'],
+ 'never': ['-Xclang', '-fno-color-diagnostics'],
+ }
+
base_options = {'b_pch': coredata.UserBooleanOption('b_pch', 'Use precompiled headers', True),
'b_lto': coredata.UserBooleanOption('b_lto', 'Use link time optimization', False),
'b_sanitize': coredata.UserComboOption('b_sanitize',
@@ -2399,11 +2404,9 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler):
class GnuObjCCompiler(GnuCompiler, ObjCCompiler):
- def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None):
+ def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None):
ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
- # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug
- # if this breaks your use case.
- GnuCompiler.__init__(self, GCC_STANDARD, defines)
+ GnuCompiler.__init__(self, gcc_type, defines)
default_warn_args = ['-Wall', '-Winvalid-pch']
self.warn_args = {'1': default_warn_args,
'2': default_warn_args + ['-Wextra'],
@@ -2411,11 +2414,9 @@ class GnuObjCCompiler(GnuCompiler, ObjCCompiler):
class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler):
- def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None):
+ def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None):
ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
- # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug
- # if this breaks your use case.
- GnuCompiler.__init__(self, GCC_STANDARD, defines)
+ GnuCompiler.__init__(self, gcc_type, defines)
default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
self.warn_args = {'1': default_warn_args,
'2': default_warn_args + ['-Wextra'],
@@ -2427,7 +2428,7 @@ class ClangCompiler:
self.id = 'clang'
self.clang_type = clang_type
self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage',
- 'b_ndebug', 'b_staticpic']
+ 'b_ndebug', 'b_staticpic', 'b_colorout']
if self.clang_type != CLANG_OSX:
self.base_options.append('b_lundef')
self.base_options.append('b_asneeded')
@@ -2439,6 +2440,9 @@ class ClangCompiler:
return [] # On Window and OS X, pic is always on.
return ['-fPIC']
+ def get_colorout_args(self, colortype):
+ return clang_color_args[colortype][:]
+
def get_buildtype_args(self, buildtype):
return gnulike_buildtype_args[buildtype]
@@ -2541,13 +2545,13 @@ class ClangCPPCompiler(ClangCompiler, CPPCompiler):
class ClangObjCCompiler(ClangCompiler, GnuObjCCompiler):
def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None):
- GnuObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
+ GnuObjCCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper)
ClangCompiler.__init__(self, cltype)
self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
class ClangObjCPPCompiler(ClangCompiler, GnuObjCPPCompiler):
def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None):
- GnuObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
+ GnuObjCPPCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper)
ClangCompiler.__init__(self, cltype)
self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage']
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index d39f161..0e6685c 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -304,7 +304,6 @@ forbidden_target_names = {'clean': None,
'PHONY': None,
'all': None,
'test': None,
- 'test:': None,
'benchmark': None,
'install': None,
'uninstall': None,
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py
index c894b0e..e4317f1 100644
--- a/mesonbuild/dependencies.py
+++ b/mesonbuild/dependencies.py
@@ -1,4 +1,4 @@
-# Copyright 2013-2015 The Meson development team
+# Copyright 2013-2017 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.
@@ -120,8 +120,8 @@ class PkgConfigDependency(Dependency):
if self.required:
raise DependencyException('Pkg-config binary missing from cross file')
else:
- potential_pkgbin = ExternalProgram(environment.cross_info.config['binaries'].get('pkgconfig', 'non_existing_binary'),
- silent=True)
+ pkgname = environment.cross_info.config['binaries']['pkgconfig']
+ potential_pkgbin = ExternalProgram(pkgname, silent=True)
if potential_pkgbin.found():
# FIXME, we should store all pkg-configs in ExternalPrograms.
# However that is too destabilizing a change to do just before release.
@@ -402,24 +402,28 @@ class WxDependency(Dependency):
return self.is_found
class ExternalProgram:
- windows_exts = ('exe', 'com', 'bat')
+ windows_exts = ('exe', 'msc', 'com', 'bat')
- def __init__(self, name, fullpath=None, silent=False, search_dir=None):
+ def __init__(self, name, command=None, silent=False, search_dir=None):
self.name = name
- if fullpath is not None:
- if not isinstance(fullpath, list):
- self.fullpath = [fullpath]
+ if command is not None:
+ if not isinstance(command, list):
+ self.command = [command]
else:
- self.fullpath = fullpath
+ self.command = command
else:
- self.fullpath = self._search(name, search_dir)
+ self.command = self._search(name, search_dir)
if not silent:
if self.found():
mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
- '(%s)' % ' '.join(self.fullpath))
+ '(%s)' % ' '.join(self.command))
else:
mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
+ def __repr__(self):
+ r = '<{} {!r} -> {!r}>'
+ return r.format(self.__class__.__name__, self.name, self.command)
+
@staticmethod
def _shebang_to_cmd(script):
"""
@@ -473,34 +477,63 @@ class ExternalProgram:
return self._shebang_to_cmd(trial)
def _search(self, name, search_dir):
+ '''
+ Search in the specified dir for the specified executable by name
+ and if not found search in PATH
+ '''
commands = self._search_dir(name, search_dir)
if commands:
return commands
# Do a standard search in PATH
- fullpath = shutil.which(name)
- if fullpath or not mesonlib.is_windows():
+ command = shutil.which(name)
+ if not mesonlib.is_windows():
# On UNIX-like platforms, the standard PATH search is enough
- return [fullpath]
- # On Windows, if name is an absolute path, we need the extension too
- for ext in self.windows_exts:
- fullpath = '{}.{}'.format(name, ext)
- if os.path.exists(fullpath):
- return [fullpath]
- # On Windows, interpreted scripts must have an extension otherwise they
- # cannot be found by a standard PATH search. So we do a custom search
- # where we manually search for a script with a shebang in PATH.
- search_dirs = os.environ.get('PATH', '').split(';')
- for search_dir in search_dirs:
- commands = self._search_dir(name, search_dir)
+ return [command]
+ # HERE BEGINS THE TERROR OF WINDOWS
+ if command:
+ # On Windows, even if the PATH search returned a full path, we can't be
+ # sure that it can be run directly if it's not a native executable.
+ # For instance, interpreted scripts sometimes need to be run explicitly
+ # with an interpreter if the file association is not done properly.
+ name_ext = os.path.splitext(command)[1]
+ if name_ext[1:].lower() in self.windows_exts:
+ # Good, it can be directly executed
+ return [command]
+ # Try to extract the interpreter from the shebang
+ commands = self._shebang_to_cmd(command)
if commands:
return commands
+ else:
+ # Maybe the name is an absolute path to a native Windows
+ # executable, but without the extension. This is technically wrong,
+ # but many people do it because it works in the MinGW shell.
+ if os.path.isabs(name):
+ for ext in self.windows_exts:
+ command = '{}.{}'.format(name, ext)
+ if os.path.exists(command):
+ return [command]
+ # On Windows, interpreted scripts must have an extension otherwise they
+ # cannot be found by a standard PATH search. So we do a custom search
+ # where we manually search for a script with a shebang in PATH.
+ search_dirs = os.environ.get('PATH', '').split(';')
+ for search_dir in search_dirs:
+ commands = self._search_dir(name, search_dir)
+ if commands:
+ return commands
return [None]
def found(self):
- return self.fullpath[0] is not None
+ return self.command[0] is not None
def get_command(self):
- return self.fullpath[:]
+ return self.command[:]
+
+ def get_path(self):
+ # Assume that the last element is the full path to the script
+ # If it's not a script, this will be an array of length 1
+ if self.found():
+ return self.command[-1]
+ return None
def get_name(self):
return self.name
@@ -531,6 +564,9 @@ class ExternalLibrary(Dependency):
def found(self):
return self.is_found
+ def get_name(self):
+ return self.name
+
def get_link_args(self):
return self.link_args
@@ -589,6 +625,9 @@ class BoostDependency(Dependency):
mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info)
else:
mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO'))
+ if 'cpp' not in self.environment.coredata.compilers:
+ raise DependencyException('Tried to use Boost but a C++ compiler is not defined.')
+ self.cpp_compiler = self.environment.coredata.compilers['cpp']
def detect_win_root(self):
globtext = 'c:\\local\\boost_*'
@@ -721,8 +760,19 @@ class BoostDependency(Dependency):
args.append('-L' + os.path.join(self.boost_root, 'lib'))
for module in self.requested_modules:
module = BoostDependency.name2lib.get(module, module)
- if module in self.lib_modules or module in self.lib_modules_mt:
- linkcmd = '-lboost_' + module
+ libname = 'boost_' + module
+ # The compiler's library detector is the most reliable so use that first.
+ default_detect = self.cpp_compiler.find_library(libname, self.environment, [])
+ if default_detect is not None:
+ if module == 'unit_testing_framework':
+ emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor')
+ else:
+ emon_args = None
+ args += default_detect
+ if emon_args is not None:
+ args += emon_args
+ elif module in self.lib_modules or module in self.lib_modules_mt:
+ linkcmd = '-l' + libname
args.append(linkcmd)
# FIXME a hack, but Boost's testing framework has a lot of
# different options and it's hard to determine what to do
@@ -980,7 +1030,7 @@ class QtBaseDependency(Dependency):
if not self.qmake.found():
continue
# Check that the qmake is for qt5
- pc, stdo = Popen_safe(self.qmake.fullpath + ['-v'])[0:2]
+ pc, stdo = Popen_safe(self.qmake.get_command() + ['-v'])[0:2]
if pc.returncode != 0:
continue
if not 'Qt version ' + self.qtver in stdo:
@@ -993,7 +1043,7 @@ class QtBaseDependency(Dependency):
return
self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0)
# Query library path, header path, and binary path
- stdo = Popen_safe(self.qmake.fullpath + ['-query'])[1]
+ stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1]
qvars = {}
for line in stdo.split('\n'):
line = line.strip()
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index e143b0b..5217626 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -19,6 +19,7 @@ from . import mlog
from .compilers import *
from .mesonlib import EnvironmentException, Popen_safe
import configparser
+import shlex
import shutil
build_filename = 'meson.build'
@@ -121,6 +122,18 @@ def detect_cpu_family(compilers):
if trial.startswith('arm'):
return 'arm'
if trial in ('amd64', 'x64'):
+ trial = 'x86_64'
+ if trial == 'x86_64':
+ # On Linux (and maybe others) there can be any mixture of 32/64 bit
+ # code in the kernel, Python, system etc. The only reliable way
+ # to know is to check the compiler defines.
+ for c in compilers.values():
+ try:
+ if c.has_define('__i386__'):
+ return 'x86'
+ except mesonlib.MesonException:
+ # Ignore compilers that do not support has_define.
+ pass
return 'x86_64'
# Add fixes here as bugs are reported.
return trial
@@ -131,6 +144,15 @@ def detect_cpu(compilers):
else:
trial = platform.machine().lower()
if trial in ('amd64', 'x64'):
+ trial = 'x86_64'
+ if trial == 'x86_64':
+ # Same check as above for cpu_family
+ for c in compilers.values():
+ try:
+ if c.has_define('__i386__'):
+ return 'i686' # All 64 bit cpus have at least this level of x86 support.
+ except mesonlib.MesonException:
+ pass
return 'x86_64'
# Add fixes here as bugs are reported.
return trial
@@ -239,6 +261,10 @@ class Environment:
self.exe_suffix = ''
self.object_suffix = 'o'
self.win_libdir_layout = False
+ if 'STRIP' in os.environ:
+ self.native_strip_bin = shlex.split('STRIP')
+ else:
+ self.native_strip_bin = ['strip']
def is_cross_build(self):
return self.cross_info is not None
@@ -345,10 +371,13 @@ class Environment:
# We ignore Cygwin for now, and treat it as a standard GCC
return GCC_STANDARD
- def detect_c_compiler(self, want_cross):
- evar = 'CC'
+ def _get_compilers(self, lang, evar, want_cross):
+ '''
+ The list of compilers is detected in the exact same way for
+ C, C++, ObjC, ObjC++, Fortran so consolidate it here.
+ '''
if self.is_cross_build() and want_cross:
- compilers = [self.cross_info.config['binaries']['c']]
+ compilers = [mesonlib.stringlistify(self.cross_info.config['binaries'][lang])]
ccache = []
is_cross = True
if self.cross_info.need_exe_wrapper():
@@ -356,122 +385,122 @@ class Environment:
else:
exe_wrap = []
elif evar in os.environ:
- compilers = os.environ[evar].split()
+ compilers = [shlex.split(os.environ[evar])]
ccache = []
is_cross = False
exe_wrap = None
else:
- compilers = self.default_c
+ compilers = getattr(self, 'default_' + lang)
ccache = self.detect_ccache()
is_cross = False
exe_wrap = None
+ return compilers, ccache, is_cross, exe_wrap
+
+ def _handle_compiler_exceptions(self, exceptions, compilers):
+ errmsg = 'Unknown compiler(s): ' + str(compilers)
+ if exceptions:
+ errmsg += '\nThe follow exceptions were encountered:'
+ for (c, e) in exceptions.items():
+ errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
+ raise EnvironmentException(errmsg)
+
+ def _detect_c_or_cpp_compiler(self, lang, evar, want_cross):
popen_exceptions = {}
+ compilers, ccache, is_cross, exe_wrap = self._get_compilers(lang, evar, want_cross)
for compiler in compilers:
+ if isinstance(compiler, str):
+ compiler = [compiler]
try:
- basename = os.path.basename(compiler).lower()
- if basename == 'cl' or basename == 'cl.exe':
+ if 'cl' in compiler or 'cl.exe' in compiler:
arg = '/?'
else:
arg = '--version'
- p, out, err = Popen_safe([compiler, arg])
+ p, out, err = Popen_safe(compiler + [arg])
except OSError as e:
- popen_exceptions[' '.join([compiler, arg])] = e
+ popen_exceptions[' '.join(compiler + [arg])] = e
continue
version = search_version(out)
if 'Free Software Foundation' in out:
- defines = self.get_gnu_compiler_defines([compiler])
+ defines = self.get_gnu_compiler_defines(compiler)
if not defines:
popen_exceptions[compiler] = 'no pre-processor defines'
continue
gtype = self.get_gnu_compiler_type(defines)
version = self.get_gnu_version_from_defines(defines)
- return GnuCCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines)
+ cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
+ return cls(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
if 'clang' in out:
if 'Apple' in out or for_darwin(want_cross, self):
cltype = CLANG_OSX
else:
cltype = CLANG_STANDARD
- return ClangCCompiler(ccache + [compiler], version, cltype, is_cross, exe_wrap)
+ cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler
+ return cls(ccache + compiler, version, cltype, is_cross, exe_wrap)
if 'Microsoft' in out or 'Microsoft' in err:
# Visual Studio prints version number to stderr but
# everything else to stdout. Why? Lord only knows.
version = search_version(err)
- return VisualStudioCCompiler([compiler], version, is_cross, exe_wrap)
+ cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
+ return cls(compiler, version, is_cross, exe_wrap)
if '(ICC)' in out:
# TODO: add microsoft add check OSX
inteltype = ICC_STANDARD
- return IntelCCompiler(ccache + [compiler], version, inteltype, is_cross, exe_wrap)
- errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"'
- if popen_exceptions:
- errmsg += '\nThe follow exceptions were encountered:'
- for (c, e) in popen_exceptions.items():
- errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
- raise EnvironmentException(errmsg)
+ cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
+ return cls(ccache + compiler, version, inteltype, is_cross, exe_wrap)
+ self._handle_compiler_exceptions(popen_exceptions, compilers)
+
+ def detect_c_compiler(self, want_cross):
+ return self._detect_c_or_cpp_compiler('c', 'CC', want_cross)
+
+ def detect_cpp_compiler(self, want_cross):
+ return self._detect_c_or_cpp_compiler('cpp', 'CXX', want_cross)
def detect_fortran_compiler(self, want_cross):
- evar = 'FC'
- if self.is_cross_build() and want_cross:
- compilers = [self.cross_info['fortran']]
- is_cross = True
- if self.cross_info.need_exe_wrapper():
- exe_wrap = self.cross_info.get('exe_wrapper', None)
- else:
- exe_wrap = []
- elif evar in os.environ:
- compilers = os.environ[evar].split()
- is_cross = False
- exe_wrap = None
- else:
- compilers = self.default_fortran
- is_cross = False
- exe_wrap = None
popen_exceptions = {}
+ compilers, ccache, is_cross, exe_wrap = self._get_compilers('fortran', 'FC', want_cross)
for compiler in compilers:
+ if isinstance(compiler, str):
+ compiler = [compiler]
for arg in ['--version', '-V']:
try:
- p, out, err = Popen_safe([compiler, arg])
+ p, out, err = Popen_safe(compiler + [arg])
except OSError as e:
- popen_exceptions[' '.join([compiler, arg])] = e
+ popen_exceptions[' '.join(compiler + [arg])] = e
continue
version = search_version(out)
if 'GNU Fortran' in out:
- defines = self.get_gnu_compiler_defines([compiler])
+ defines = self.get_gnu_compiler_defines(compiler)
if not defines:
popen_exceptions[compiler] = 'no pre-processor defines'
continue
gtype = self.get_gnu_compiler_type(defines)
version = self.get_gnu_version_from_defines(defines)
- return GnuFortranCompiler([compiler], version, gtype, is_cross, exe_wrap, defines)
+ return GnuFortranCompiler(compiler, version, gtype, is_cross, exe_wrap, defines)
if 'G95' in out:
- return G95FortranCompiler([compiler], version, is_cross, exe_wrap)
+ return G95FortranCompiler(compiler, version, is_cross, exe_wrap)
if 'Sun Fortran' in err:
version = search_version(err)
- return SunFortranCompiler([compiler], version, is_cross, exe_wrap)
+ return SunFortranCompiler(compiler, version, is_cross, exe_wrap)
if 'ifort (IFORT)' in out:
- return IntelFortranCompiler([compiler], version, is_cross, exe_wrap)
+ return IntelFortranCompiler(compiler, version, is_cross, exe_wrap)
if 'PathScale EKOPath(tm)' in err:
- return PathScaleFortranCompiler([compiler], version, is_cross, exe_wrap)
+ return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap)
if 'PGI Compilers' in out:
- return PGIFortranCompiler([compiler], version, is_cross, exe_wrap)
+ return PGIFortranCompiler(compiler, version, is_cross, exe_wrap)
if 'Open64 Compiler Suite' in err:
- return Open64FortranCompiler([compiler], version, is_cross, exe_wrap)
+ return Open64FortranCompiler(compiler, version, is_cross, exe_wrap)
if 'NAG Fortran' in err:
- return NAGFortranCompiler([compiler], version, is_cross, exe_wrap)
- errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"'
- if popen_exceptions:
- errmsg += '\nThe follow exceptions were encountered:'
- for (c, e) in popen_exceptions.items():
- errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
- raise EnvironmentException(errmsg)
+ return NAGFortranCompiler(compiler, version, is_cross, exe_wrap)
+ self._handle_compiler_exceptions(popen_exceptions, compilers)
def get_scratch_dir(self):
return self.scratch_dir
@@ -480,115 +509,57 @@ class Environment:
path = os.path.split(__file__)[0]
return os.path.join(path, 'depfixer.py')
- def detect_cpp_compiler(self, want_cross):
- evar = 'CXX'
- if self.is_cross_build() and want_cross:
- compilers = [self.cross_info.config['binaries']['cpp']]
- ccache = []
- is_cross = True
- if self.cross_info.need_exe_wrapper():
- exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None)
- else:
- exe_wrap = []
- elif evar in os.environ:
- compilers = os.environ[evar].split()
- ccache = []
- is_cross = False
- exe_wrap = None
- else:
- compilers = self.default_cpp
- ccache = self.detect_ccache()
- is_cross = False
- exe_wrap = None
+ def detect_objc_compiler(self, want_cross):
popen_exceptions = {}
+ compilers, ccache, is_cross, exe_wrap = self._get_compilers('objc', 'OBJC', want_cross)
for compiler in compilers:
- basename = os.path.basename(compiler).lower()
- if basename == 'cl' or basename == 'cl.exe':
- arg = '/?'
- else:
- arg = '--version'
+ if isinstance(compiler, str):
+ compiler = [compiler]
+ arg = ['--version']
try:
- p, out, err = Popen_safe([compiler, arg])
+ p, out, err = Popen_safe(compiler + arg)
except OSError as e:
- popen_exceptions[' '.join([compiler, arg])] = e
- continue
+ popen_exceptions[' '.join(compiler + arg)] = e
version = search_version(out)
if 'Free Software Foundation' in out:
- defines = self.get_gnu_compiler_defines([compiler])
+ defines = self.get_gnu_compiler_defines(compiler)
if not defines:
popen_exceptions[compiler] = 'no pre-processor defines'
continue
gtype = self.get_gnu_compiler_type(defines)
version = self.get_gnu_version_from_defines(defines)
- return GnuCPPCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines)
- if 'clang' in out:
- if 'Apple' in out:
- cltype = CLANG_OSX
- else:
- cltype = CLANG_STANDARD
- return ClangCPPCompiler(ccache + [compiler], version, cltype, is_cross, exe_wrap)
- if 'Microsoft' in out or 'Microsoft' in err:
- version = search_version(err)
- return VisualStudioCPPCompiler([compiler], version, is_cross, exe_wrap)
- if '(ICC)' in out:
- # TODO: add microsoft add check OSX
- inteltype = ICC_STANDARD
- return IntelCPPCompiler(ccache + [compiler], version, inteltype, is_cross, exe_wrap)
- errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"'
- if popen_exceptions:
- errmsg += '\nThe follow exceptions were encountered:'
- for (c, e) in popen_exceptions.items():
- errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
- raise EnvironmentException(errmsg)
-
- def detect_objc_compiler(self, want_cross):
- if self.is_cross_build() and want_cross:
- exelist = [self.cross_info['objc']]
- is_cross = True
- if self.cross_info.need_exe_wrapper():
- exe_wrap = self.cross_info.get('exe_wrapper', None)
- else:
- exe_wrap = []
- else:
- exelist = self.get_objc_compiler_exelist()
- is_cross = False
- exe_wrap = None
- try:
- p, out, err = Popen_safe(exelist + ['--version'])
- except OSError:
- raise EnvironmentException('Could not execute ObjC compiler "%s"' % ' '.join(exelist))
- version = search_version(out)
- if 'Free Software Foundation' in out:
- defines = self.get_gnu_compiler_defines(exelist)
- version = self.get_gnu_version_from_defines(defines)
- return GnuObjCCompiler(exelist, version, is_cross, exe_wrap, defines)
- if out.startswith('Apple LLVM'):
- return ClangObjCCompiler(exelist, version, CLANG_OSX, is_cross, exe_wrap)
- raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
+ return GnuObjCCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
+ if out.startswith('Apple LLVM'):
+ return ClangObjCCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
+ if out.startswith('clang'):
+ return ClangObjCCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
+ self._handle_compiler_exceptions(popen_exceptions, compilers)
def detect_objcpp_compiler(self, want_cross):
- if self.is_cross_build() and want_cross:
- exelist = [self.cross_info['objcpp']]
- is_cross = True
- if self.cross_info.need_exe_wrapper():
- exe_wrap = self.cross_info.get('exe_wrapper', None)
- else:
- exe_wrap = []
- else:
- exelist = self.get_objcpp_compiler_exelist()
- is_cross = False
- exe_wrap = None
- try:
- p, out, err = Popen_safe(exelist + ['--version'])
- except OSError:
- raise EnvironmentException('Could not execute ObjC++ compiler "%s"' % ' '.join(exelist))
- version = search_version(out)
- if 'Free Software Foundation' in out:
- defines = self.get_gnu_compiler_defines(exelist)
- return GnuObjCPPCompiler(exelist, version, is_cross, exe_wrap, defines)
- if out.startswith('Apple LLVM'):
- return ClangObjCPPCompiler(exelist, version, CLANG_OSX, is_cross, exe_wrap)
- raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"')
+ popen_exceptions = {}
+ compilers, ccache, is_cross, exe_wrap = self._get_compilers('objcpp', 'OBJCXX', want_cross)
+ for compiler in compilers:
+ if isinstance(compiler, str):
+ compiler = [compiler]
+ arg = ['--version']
+ try:
+ p, out, err = Popen_safe(compiler + arg)
+ except OSError as e:
+ popen_exceptions[' '.join(compiler + arg)] = e
+ version = search_version(out)
+ if 'Free Software Foundation' in out:
+ defines = self.get_gnu_compiler_defines(compiler)
+ if not defines:
+ popen_exceptions[compiler] = 'no pre-processor defines'
+ continue
+ gtype = self.get_gnu_compiler_type(defines)
+ version = self.get_gnu_version_from_defines(defines)
+ return GnuObjCPPCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines)
+ if out.startswith('Apple LLVM'):
+ return ClangObjCPPCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap)
+ if out.startswith('clang'):
+ return ClangObjCPPCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap)
+ self._handle_compiler_exceptions(popen_exceptions, compilers)
def detect_java_compiler(self):
exelist = ['javac']
@@ -641,9 +612,9 @@ class Environment:
# environment variable because LDC has a much more
# up to date language version at time (2016).
if 'DC' in os.environ:
- exelist = os.environ['DC'].split()
+ exelist = shlex.split(os.environ['DC'])
elif self.is_cross_build() and want_cross:
- exelist = [self.cross_info.config['binaries']['d']]
+ exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d'])
is_cross = True
elif shutil.which("ldc2"):
exelist = ['ldc2']
@@ -683,30 +654,31 @@ class Environment:
def detect_static_linker(self, compiler):
if compiler.is_cross:
linker = self.cross_info.config['binaries']['ar']
+ if isinstance(linker, str):
+ linker = [linker]
else:
evar = 'AR'
if evar in os.environ:
- linker = os.environ[evar].strip()
+ linker = shlex.split(os.environ[evar])
elif isinstance(compiler, VisualStudioCCompiler):
- linker = self.vs_static_linker
+ linker = [self.vs_static_linker]
else:
- linker = self.default_static_linker
- basename = os.path.basename(linker).lower()
- if basename == 'lib' or basename == 'lib.exe':
+ linker = [self.default_static_linker]
+ if 'lib' in linker or 'lib.exe' in linker:
arg = '/?'
else:
arg = '--version'
try:
- p, out, err = Popen_safe([linker, arg])
+ p, out, err = Popen_safe(linker + [arg])
except OSError:
- raise EnvironmentException('Could not execute static linker "%s".' % linker)
+ raise EnvironmentException('Could not execute static linker "%s".' % ' '.join(linker))
if '/OUT:' in out or '/OUT:' in err:
- return VisualStudioLinker([linker])
+ return VisualStudioLinker(linker)
if p.returncode == 0:
- return ArLinker([linker])
+ return ArLinker(linker)
if p.returncode == 1 and err.startswith('usage'): # OSX
- return ArLinker([linker])
- raise EnvironmentException('Unknown static linker "%s"' % linker)
+ return ArLinker(linker)
+ raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linker))
def detect_ccache(self):
try:
@@ -719,20 +691,6 @@ class Environment:
cmdlist = []
return cmdlist
- def get_objc_compiler_exelist(self):
- ccachelist = self.detect_ccache()
- evar = 'OBJCC'
- if evar in os.environ:
- return os.environ[evar].split()
- return ccachelist + self.default_objc
-
- def get_objcpp_compiler_exelist(self):
- ccachelist = self.detect_ccache()
- evar = 'OBJCXX'
- if evar in os.environ:
- return os.environ[evar].split()
- return ccachelist + self.default_objcpp
-
def get_source_dir(self):
return self.source_dir
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 4466f22..07b5c40 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -134,6 +134,10 @@ class EnvironmentVariablesHolder(MutableInterpreterObject):
'prepend': self.prepend_method,
})
+ def __repr__(self):
+ repr_str = "<{0}: {1}>"
+ return repr_str.format(self.__class__.__name__, self.held_object.envvars)
+
@stringArgs
def add_var(self, method, args, kwargs):
if not isinstance(kwargs.get("separator", ""), str):
@@ -285,16 +289,16 @@ class ExternalProgramHolder(InterpreterObject):
return self.found()
def path_method(self, args, kwargs):
- return self.get_command()
+ return self.held_object.get_path()
def found(self):
return self.held_object.found()
def get_command(self):
- return self.held_object.fullpath
+ return self.held_object.get_command()
def get_name(self):
- return self.held_object.name
+ return self.held_object.get_name()
class ExternalLibraryHolder(InterpreterObject):
def __init__(self, el):
@@ -308,9 +312,6 @@ class ExternalLibraryHolder(InterpreterObject):
def found_method(self, args, kwargs):
return self.found()
- def get_filename(self):
- return self.held_object.fullpath
-
def get_name(self):
return self.held_object.name
@@ -1424,7 +1425,8 @@ class Interpreter(InterpreterBase):
elif isinstance(cmd, str):
cmd = [cmd]
else:
- raise InterpreterException('First argument is of incorrect type.')
+ raise InterpreterException('First argument should be find_program() '
+ 'or string, not {!r}'.format(cmd))
expanded_args = []
for a in mesonlib.flatten(cargs):
if isinstance(a, str):
@@ -1759,7 +1761,6 @@ class Interpreter(InterpreterBase):
break
self.coredata.base_options[optname] = oobj
- @stringArgs
def func_find_program(self, node, args, kwargs):
if len(args) == 0:
raise InterpreterException('No program name specified.')
@@ -1769,8 +1770,21 @@ class Interpreter(InterpreterBase):
# Search for scripts relative to current subdir.
# Do not cache found programs because find_program('foobar')
# might give different results when run from different source dirs.
- search_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
+ source_dir = os.path.join(self.environment.get_source_dir(), self.subdir)
for exename in args:
+ if isinstance(exename, mesonlib.File):
+ if exename.is_built:
+ search_dir = os.path.join(self.environment.get_build_dir(),
+ exename.subdir)
+ else:
+ search_dir = os.path.join(self.environment.get_source_dir(),
+ exename.subdir)
+ exename = exename.fname
+ elif isinstance(exename, str):
+ search_dir = source_dir
+ else:
+ raise InvalidArguments('find_program only accepts strings and '
+ 'files, not {!r}'.format(exename))
extprog = dependencies.ExternalProgram(exename, search_dir=search_dir)
progobj = ExternalProgramHolder(extprog)
if progobj.found():
@@ -2046,7 +2060,7 @@ requirements use the version keyword argument instead.''')
self.add_test(node, args, kwargs, True)
def unpack_env_kwarg(self, kwargs):
- envlist = kwargs.get('env', [])
+ envlist = kwargs.get('env', EnvironmentVariablesHolder())
if isinstance(envlist, EnvironmentVariablesHolder):
env = envlist.held_object
else:
@@ -2132,6 +2146,8 @@ requirements use the version keyword argument instead.''')
raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.')
prev_subdir = self.subdir
subdir = os.path.join(prev_subdir, args[0])
+ if os.path.isabs(subdir):
+ raise InvalidArguments('Subdir argument must be a relative path.')
if subdir in self.visited_subdirs:
raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'
% subdir)
@@ -2212,12 +2228,28 @@ requirements use the version keyword argument instead.''')
raise InterpreterException("configure_file takes only keyword arguments.")
if 'output' not in kwargs:
raise InterpreterException('Required keyword argument "output" not defined.')
- inputfile = kwargs.get('input', None)
+ if 'configuration' in kwargs and 'command' in kwargs:
+ raise InterpreterException('Must not specify both "configuration" '
+ 'and "command" keyword arguments since '
+ 'they are mutually exclusive.')
+ # Validate input
+ inputfile = None
+ if 'input' in kwargs:
+ inputfile = kwargs['input']
+ if isinstance(inputfile, list):
+ if len(inputfile) != 1:
+ m = "Keyword argument 'input' requires exactly one file"
+ raise InterpreterException(m)
+ inputfile = inputfile[0]
+ if not isinstance(inputfile, (str, mesonlib.File)):
+ raise InterpreterException('Input must be a string or a file')
+ ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile)
+ elif 'command' in kwargs:
+ raise InterpreterException('Required keyword argument \'input\' missing')
+ # Validate output
output = kwargs['output']
- if not isinstance(inputfile, (str, type(None))):
- raise InterpreterException('Input must be a string.')
if not isinstance(output, str):
- raise InterpreterException('Output must be a string.')
+ raise InterpreterException('Output file name must be a string')
if os.path.split(output)[0] != '':
raise InterpreterException('Output file name must not contain a subdirectory.')
(ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output))
@@ -2226,6 +2258,7 @@ requirements use the version keyword argument instead.''')
conf = kwargs['configuration']
if not isinstance(conf, ConfigurationDataHolder):
raise InterpreterException('Argument "configuration" is not of type configuration_data')
+ mlog.log('Configuring', mlog.bold(output), 'using configuration')
if inputfile is not None:
# Normalize the path of the conffile to avoid duplicates
# This is especially important to convert '/' to '\' on Windows
@@ -2233,15 +2266,19 @@ requirements use the version keyword argument instead.''')
if conffile not in self.build_def_files:
self.build_def_files.append(conffile)
os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True)
- ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile)
mesonlib.do_conf_file(ifile_abs, ofile_abs, conf.held_object)
else:
mesonlib.dump_conf_header(ofile_abs, conf.held_object)
conf.mark_used()
elif 'command' in kwargs:
- if 'input' not in kwargs:
- raise InterpreterException('Required keyword input missing.')
- res = self.func_run_command(node, kwargs['command'], {})
+ # We use absolute paths for input and output here because the cwd
+ # that the command is run from is 'unspecified', so it could change.
+ # Currently it's builddir/subdir for in_builddir else srcdir/subdir.
+ values = mesonlib.get_filenames_templates_dict([ifile_abs], [ofile_abs])
+ # Substitute @INPUT@, @OUTPUT@, etc here.
+ cmd = mesonlib.substitute_values(kwargs['command'], values)
+ mlog.log('Configuring', mlog.bold(output), 'with command')
+ res = self.func_run_command(node, cmd, {'in_builddir': True})
if res.returncode != 0:
raise InterpreterException('Running configure command failed.\n%s\n%s' %
(res.stdout, res.stderr))
@@ -2255,8 +2292,27 @@ requirements use the version keyword argument instead.''')
@stringArgs
def func_include_directories(self, node, args, kwargs):
- absbase = os.path.join(self.environment.get_source_dir(), self.subdir)
+ src_root = self.environment.get_source_dir()
+ absbase = os.path.join(src_root, self.subdir)
for a in args:
+ if a.startswith(src_root):
+ raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use
+relative paths instead.
+
+To get include path to any directory relative to the current dir do
+
+incdir = include_directories(dirname)
+
+After this incdir will contain both the current source dir as well as the
+corresponding build dir. It can then be used in any subdirectory and
+Meson will take care of all the busywork to make paths work.
+
+Dirname can even be '.' to mark the current directory. Though you should
+remember that the current source and build directories are always
+put in the include directories by default so you only need to do
+include_directories('.') if you intend to use the result in a
+different subdirectory.
+''')
absdir = os.path.join(absbase, a)
if not os.path.isdir(absdir):
raise InvalidArguments('Include dir %s does not exist.' % a)
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index f0b20e1..c7368d5 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -521,3 +521,154 @@ def commonpath(paths):
new = os.path.join(*new)
common = pathlib.PurePath(new)
return str(common)
+
+def iter_regexin_iter(regexiter, initer):
+ '''
+ Takes each regular expression in @regexiter and tries to search for it in
+ every item in @initer. If there is a match, returns that match.
+ Else returns False.
+ '''
+ for regex in regexiter:
+ for ii in initer:
+ if not isinstance(ii, str):
+ continue
+ match = re.search(regex, ii)
+ if match:
+ return match.group()
+ return False
+
+def _substitute_values_check_errors(command, values):
+ # Error checking
+ inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@')
+ outregex = ('@OUTPUT([0-9]+)?@', '@OUTDIR@')
+ if '@INPUT@' not in values:
+ # Error out if any input-derived templates are present in the command
+ match = iter_regexin_iter(inregex, command)
+ if match:
+ m = 'Command cannot have {!r}, since no input files were specified'
+ raise MesonException(m.format(match))
+ else:
+ if len(values['@INPUT@']) > 1:
+ # Error out if @PLAINNAME@ or @BASENAME@ is present in the command
+ match = iter_regexin_iter(inregex[1:], command)
+ if match:
+ raise MesonException('Command cannot have {!r} when there is '
+ 'more than one input file'.format(match))
+ # Error out if an invalid @INPUTnn@ template was specified
+ for each in command:
+ if not isinstance(each, str):
+ continue
+ match = re.search(inregex[0], each)
+ if match and match.group() not in values:
+ m = 'Command cannot have {!r} since there are only {!r} inputs'
+ raise MesonException(m.format(match.group(), len(values['@INPUT@'])))
+ if '@OUTPUT@' not in values:
+ # Error out if any output-derived templates are present in the command
+ match = iter_regexin_iter(outregex, command)
+ if match:
+ m = 'Command cannot have {!r} since there are no outputs'
+ raise MesonException(m.format(match))
+ else:
+ # Error out if an invalid @OUTPUTnn@ template was specified
+ for each in command:
+ if not isinstance(each, str):
+ continue
+ match = re.search(outregex[0], each)
+ if match and match.group() not in values:
+ m = 'Command cannot have {!r} since there are only {!r} outputs'
+ raise MesonException(m.format(match.group(), len(values['@OUTPUT@'])))
+
+def substitute_values(command, values):
+ '''
+ Substitute the template strings in the @values dict into the list of
+ strings @command and return a new list. For a full list of the templates,
+ see get_filenames_templates_dict()
+
+ If multiple inputs/outputs are given in the @values dictionary, we
+ substitute @INPUT@ and @OUTPUT@ only if they are the entire string, not
+ just a part of it, and in that case we substitute *all* of them.
+ '''
+ # Error checking
+ _substitute_values_check_errors(command, values)
+ # Substitution
+ outcmd = []
+ for vv in command:
+ if not isinstance(vv, str):
+ outcmd.append(vv)
+ elif '@INPUT@' in vv:
+ inputs = values['@INPUT@']
+ if vv == '@INPUT@':
+ outcmd += inputs
+ elif len(inputs) == 1:
+ outcmd.append(vv.replace('@INPUT@', inputs[0]))
+ else:
+ raise MesonException("Command has '@INPUT@' as part of a "
+ "string and more than one input file")
+ elif '@OUTPUT@' in vv:
+ outputs = values['@OUTPUT@']
+ if vv == '@OUTPUT@':
+ outcmd += outputs
+ elif len(outputs) == 1:
+ outcmd.append(vv.replace('@OUTPUT@', outputs[0]))
+ else:
+ raise MesonException("Command has '@OUTPUT@' as part of a "
+ "string and more than one output file")
+ # Append values that are exactly a template string.
+ # This is faster than a string replace.
+ elif vv in values:
+ outcmd.append(values[vv])
+ # Substitute everything else with replacement
+ else:
+ for key, value in values.items():
+ if key in ('@INPUT@', '@OUTPUT@'):
+ # Already done above
+ continue
+ vv = vv.replace(key, value)
+ outcmd.append(vv)
+ return outcmd
+
+def get_filenames_templates_dict(inputs, outputs):
+ '''
+ Create a dictionary with template strings as keys and values as values for
+ the following templates:
+
+ @INPUT@ - the full path to one or more input files, from @inputs
+ @OUTPUT@ - the full path to one or more output files, from @outputs
+ @OUTDIR@ - the full path to the directory containing the output files
+
+ If there is only one input file, the following keys are also created:
+
+ @PLAINNAME@ - the filename of the input file
+ @BASENAME@ - the filename of the input file with the extension removed
+
+ If there is more than one input file, the following keys are also created:
+
+ @INPUT0@, @INPUT1@, ... one for each input file
+
+ If there is more than one output file, the following keys are also created:
+
+ @OUTPUT0@, @OUTPUT1@, ... one for each output file
+ '''
+ values = {}
+ # Gather values derived from the input
+ if inputs:
+ # We want to substitute all the inputs.
+ values['@INPUT@'] = inputs
+ for (ii, vv) in enumerate(inputs):
+ # Write out @INPUT0@, @INPUT1@, ...
+ values['@INPUT{}@'.format(ii)] = vv
+ if len(inputs) == 1:
+ # Just one value, substitute @PLAINNAME@ and @BASENAME@
+ values['@PLAINNAME@'] = plain = os.path.split(inputs[0])[1]
+ values['@BASENAME@'] = os.path.splitext(plain)[0]
+ if outputs:
+ # Gather values derived from the outputs, similar to above.
+ values['@OUTPUT@'] = outputs
+ for (ii, vv) in enumerate(outputs):
+ values['@OUTPUT{}@'.format(ii)] = vv
+ # Outdir should be the same for all outputs
+ values['@OUTDIR@'] = os.path.split(outputs[0])[0]
+ # Many external programs fail on empty arguments.
+ if values['@OUTDIR@'] == '':
+ values['@OUTDIR@'] = '.'
+ return values
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index e30500f..6eab76e 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -23,6 +23,7 @@ import json, pickle
from . import coredata, build
import argparse
import sys, os
+import pathlib
parser = argparse.ArgumentParser()
parser.add_argument('--targets', action='store_true', dest='list_targets', default=False,
@@ -56,7 +57,9 @@ def determine_installed_path(target, installdata):
fname = i[0]
outdir = i[1]
outname = os.path.join(installdata.prefix, outdir, os.path.split(fname)[-1])
- return outname
+ # Normalize the path by using os.path.sep consistently, etc.
+ # Does not change the effective path.
+ return str(pathlib.PurePath(outname))
def list_installed(installdata):
@@ -111,23 +114,11 @@ def list_target_files(target_name, coredata, builddata):
print(json.dumps(sources))
def list_buildoptions(coredata, builddata):
- buildtype = {'choices': ['plain', 'debug', 'debugoptimized', 'release', 'minsize'],
- 'type': 'combo',
- 'value': coredata.get_builtin_option('buildtype'),
- 'description': 'Build type',
- 'name': 'type'}
- strip = {'value': coredata.get_builtin_option('strip'),
- 'type': 'boolean',
- 'description': 'Strip on install',
- 'name': 'strip'}
- unity = {'value': coredata.get_builtin_option('unity'),
- 'type': 'boolean',
- 'description': 'Unity build',
- 'name': 'unity'}
- optlist = [buildtype, strip, unity]
+ optlist = []
add_keys(optlist, coredata.user_options)
add_keys(optlist, coredata.compiler_options)
add_keys(optlist, coredata.base_options)
+ add_keys(optlist, coredata.builtins)
print(json.dumps(optlist))
def add_keys(optlist, options):
diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py
index a4f93d0..843f366 100644
--- a/mesonbuild/mlog.py
+++ b/mesonbuild/mlog.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import sys, os, platform
+import sys, os, platform, io
"""This is (mostly) a standalone module used to write logging
information about Meson runs. Some output goes to screen,
@@ -25,7 +25,7 @@ log_file = None
def initialize(logdir):
global log_dir, log_file
log_dir = logdir
- log_file = open(os.path.join(logdir, 'meson-log.txt'), 'w')
+ log_file = open(os.path.join(logdir, 'meson-log.txt'), 'w', encoding='utf8')
def shutdown():
global log_file
@@ -70,6 +70,17 @@ def process_markup(args, keep):
arr.append(str(arg))
return arr
+def force_print(*args, **kwargs):
+ # _Something_ is going to get printed.
+ try:
+ print(*args, **kwargs)
+ except UnicodeEncodeError:
+ iostr = io.StringIO()
+ kwargs['file'] = iostr
+ print(*args, **kwargs)
+ cleaned = iostr.getvalue().encode('ascii', 'replace').decode('ascii')
+ print(cleaned)
+
def debug(*args, **kwargs):
arr = process_markup(args, False)
if log_file is not None:
@@ -83,7 +94,7 @@ def log(*args, **kwargs):
log_file.flush()
if colorize_console:
arr = process_markup(args, True)
- print(*arr, **kwargs)
+ force_print(*arr, **kwargs)
def warning(*args, **kwargs):
log(yellow('WARNING:'), *args, **kwargs)
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 2a54f3a..8cf89e1 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -37,7 +37,7 @@ from . import ExtensionModule
#
# https://github.com/ninja-build/ninja/issues/1184
# https://bugzilla.gnome.org/show_bug.cgi?id=774368
-gresource_dep_needed_version = '>= 2.52.0'
+gresource_dep_needed_version = '>= 2.51.1'
native_glib_version = None
girwarning_printed = False
@@ -74,14 +74,15 @@ class GnomeModule(ExtensionModule):
global gresource_warning_printed
if not gresource_warning_printed:
if not mesonlib.version_compare(self._get_native_glib_version(state), gresource_dep_needed_version):
- mlog.warning('''GLib compiled dependencies do not work reliably with
-the current version of GLib. See the following upstream issue:''',
+ mlog.warning('GLib compiled dependencies do not work reliably with \n'
+ 'the current version of GLib. See the following upstream issue:',
mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368'))
gresource_warning_printed = True
return []
def compile_resources(self, state, args, kwargs):
self.__print_gresources_warning(state)
+ glib_version = self._get_native_glib_version(state)
cmd = ['glib-compile-resources', '@INPUT@']
@@ -90,26 +91,48 @@ the current version of GLib. See the following upstream issue:''',
source_dirs = [source_dirs]
if len(args) < 2:
- raise MesonException('Not enough arguments; The name of the resource and the path to the XML file are required')
+ raise MesonException('Not enough arguments; the name of the resource '
+ 'and the path to the XML file are required')
dependencies = kwargs.pop('dependencies', [])
if not isinstance(dependencies, list):
dependencies = [dependencies]
-
- glib_version = self._get_native_glib_version(state)
- if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
- if len(dependencies) > 0:
- raise MesonException('''The "dependencies" argument of gnome.compile_resources()
-can not be used with the current version of glib-compiled-resources, due to
-<https://bugzilla.gnome.org/show_bug.cgi?id=774368>''')
+ # Validate dependencies
+ for (ii, dep) in enumerate(dependencies):
+ if hasattr(dep, 'held_object'):
+ dependencies[ii] = dep = dep.held_object
+ if not isinstance(dep, (mesonlib.File, build.CustomTarget)):
+ m = 'Unexpected dependency type {!r} for gnome.compile_resources() ' \
+ '"dependencies" argument.\nPlease pass the return value of ' \
+ 'custom_target() or configure_file()'
+ raise MesonException(m.format(dep))
+ if isinstance(dep, build.CustomTarget):
+ if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
+ m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \
+ 'be used with the current version of glib-compile-resources due to\n' \
+ '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>'
+ raise MesonException(m)
ifile = args[1]
if isinstance(ifile, mesonlib.File):
- ifile = os.path.join(ifile.subdir, ifile.fname)
+ # glib-compile-resources will be run inside the source dir,
+ # so we need either 'src_to_build' or the absolute path.
+ # Absolute path is the easiest choice.
+ if ifile.is_built:
+ ifile = os.path.join(state.environment.get_build_dir(), ifile.subdir, ifile.fname)
+ else:
+ ifile = os.path.join(ifile.subdir, ifile.fname)
elif isinstance(ifile, str):
ifile = os.path.join(state.subdir, ifile)
+ elif isinstance(ifile, (interpreter.CustomTargetHolder,
+ interpreter.GeneratedObjectsHolder)):
+ m = 'Resource xml files generated at build-time cannot be used ' \
+ 'with gnome.compile_resources() because we need to scan ' \
+ 'the xml for dependencies. Use configure_file() instead ' \
+ 'to generate it at configure-time.'
+ raise MesonException(m)
else:
- raise RuntimeError('Unreachable code.')
+ raise MesonException('Invalid file argument: {!r}'.format(ifile))
depend_files, depends, subdirs = self._get_gresource_dependencies(
state, ifile, source_dirs, dependencies)
@@ -183,13 +206,6 @@ can not be used with the current version of glib-compiled-resources, due to
return ModuleReturnValue(rv, rv)
def _get_gresource_dependencies(self, state, input_file, source_dirs, dependencies):
- for dep in dependencies:
- if not isinstance(dep, interpreter.CustomTargetHolder) and not \
- isinstance(dep, mesonlib.File):
- raise MesonException(
- 'Unexpected dependency type for gnome.compile_resources() '
- '"dependencies" argument. Please pass the output of '
- 'custom_target() or configure_file().')
cmd = ['glib-compile-resources',
input_file,
@@ -199,9 +215,10 @@ can not be used with the current version of glib-compiled-resources, due to
cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)]
cmd += ['--sourcedir', state.subdir] # Current dir
- pc, stdout = Popen_safe(cmd, cwd=state.environment.get_source_dir())[0:2]
+ pc, stdout, stderr = Popen_safe(cmd, cwd=state.environment.get_source_dir())
if pc.returncode != 0:
- mlog.warning('glib-compile-resources has failed to get the dependencies for {}'.format(cmd[1]))
+ m = 'glib-compile-resources failed to get dependencies for {}:\n{}'
+ mlog.warning(m.format(cmd[1], stderr))
raise subprocess.CalledProcessError(pc.returncode, cmd)
dep_files = stdout.split('\n')[:-1]
@@ -354,11 +371,7 @@ can not be used with the current version of glib-compiled-resources, due to
{'native': True})
pkgargs = self.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 ModuleReturnValue(None, [])
+ raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.')
ns = kwargs.pop('namespace')
nsversion = kwargs.pop('nsversion')
libsources = kwargs.pop('sources')
@@ -366,7 +379,7 @@ can not be used with the current version of glib-compiled-resources, due to
depends = [girtarget]
gir_inc_dirs = []
- scan_command = giscanner.get_command() + ['@INPUT@']
+ scan_command = [giscanner, '@INPUT@']
scan_command += pkgargs
scan_command += ['--no-libtool', '--namespace=' + ns, '--nsversion=' + nsversion, '--warn-all',
'--output', '@OUTPUT@']
@@ -522,7 +535,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 = gicompiler.get_command() + [scan_target, '--output', '@OUTPUT@']
+ typelib_cmd = [gicompiler, scan_target, '--output', '@OUTPUT@']
typelib_cmd += get_include_args(state.environment, gir_inc_dirs,
prefix='--includedir=')
for incdir in typelib_includes:
@@ -546,7 +559,7 @@ can not be used with the current version of glib-compiled-resources, due to
srcdir = os.path.join(state.build_to_src, state.subdir)
outdir = state.subdir
- cmd = find_program('glib-compile-schemas', 'gsettings-compile').get_command()
+ cmd = [find_program('glib-compile-schemas', 'gsettings-compile')]
cmd += ['--targetdir', outdir, srcdir]
kwargs['command'] = cmd
kwargs['input'] = []
@@ -740,7 +753,7 @@ can not be used with the current version of glib-compiled-resources, due to
namebase = args[0]
xml_file = args[1]
target_name = namebase + '-gdbus'
- cmd = find_program('gdbus-codegen', target_name).get_command()
+ cmd = [find_program('gdbus-codegen', target_name)]
if 'interface_prefix' in kwargs:
cmd += ['--interface-prefix', kwargs.pop('interface_prefix')]
if 'namespace' in kwargs:
@@ -798,7 +811,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 = find_program('glib-mkenums', 'mkenums').get_command() + cmd
+ cmd = [find_program('glib-mkenums', 'mkenums')] + cmd
custom_kwargs = {}
for arg in known_custom_target_kwargs:
if arg in kwargs:
@@ -863,6 +876,7 @@ can not be used with the current version of glib-compiled-resources, due to
}
custom_kwargs.update(kwargs)
return build.CustomTarget(output, state.subdir, custom_kwargs,
+ # https://github.com/mesonbuild/meson/issues/973
absolute_paths=True)
def genmarshal(self, state, args, kwargs):
@@ -880,7 +894,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 = find_program('glib-genmarshal', output + '_genmarshal').get_command()
+ cmd = [find_program('glib-genmarshal', output + '_genmarshal')]
known_kwargs = ['internal', 'nostdinc', 'skip_source', 'stdinc',
'valist_marshallers']
known_custom_target_kwargs = ['build_always', 'depends',
@@ -1009,7 +1023,7 @@ 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 = find_program('vapigen', 'Vaapi').get_command()
+ cmd = [find_program('vapigen', 'Vaapi')]
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)
diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py
index 33c9f80..7146739 100644
--- a/mesonbuild/modules/qt4.py
+++ b/mesonbuild/modules/qt4.py
@@ -48,7 +48,7 @@ class Qt4Module(ExtensionModule):
raise MesonException('Moc preprocessor is not for Qt 4. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' %
- (' '.join(self.moc.fullpath), moc_ver.split()[-1]))
+ (self.moc.get_path(), moc_ver.split()[-1]))
else:
mlog.log(' moc:', mlog.red('NO'))
if self.uic.found():
@@ -61,7 +61,7 @@ class Qt4Module(ExtensionModule):
raise MesonException('Uic compiler is not for Qt4. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' %
- (' '.join(self.uic.fullpath), uic_ver.split()[-1]))
+ (self.uic.get_path(), uic_ver.split()[-1]))
else:
mlog.log(' uic:', mlog.red('NO'))
if self.rcc.found():
@@ -74,7 +74,7 @@ class Qt4Module(ExtensionModule):
raise MesonException('Rcc compiler is not for Qt 4. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)'
- % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1]))
+ % (self.rcc.get_path(), rcc_ver.split()[-1]))
else:
mlog.log(' rcc:', mlog.red('NO'))
self.tools_detected = True
diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py
index b4f1475..2a87a80 100644
--- a/mesonbuild/modules/qt5.py
+++ b/mesonbuild/modules/qt5.py
@@ -50,7 +50,7 @@ class Qt5Module(ExtensionModule):
raise MesonException('Moc preprocessor is not for Qt 5. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' %
- (' '.join(self.moc.fullpath), moc_ver.split()[-1]))
+ (self.moc.get_path(), moc_ver.split()[-1]))
else:
mlog.log(' moc:', mlog.red('NO'))
if self.uic.found():
@@ -65,7 +65,7 @@ class Qt5Module(ExtensionModule):
raise MesonException('Uic compiler is not for Qt 5. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' %
- (' '.join(self.uic.fullpath), uic_ver.split()[-1]))
+ (self.uic.get_path(), uic_ver.split()[-1]))
else:
mlog.log(' uic:', mlog.red('NO'))
if self.rcc.found():
@@ -80,7 +80,7 @@ class Qt5Module(ExtensionModule):
raise MesonException('Rcc compiler is not for Qt 5. Output:\n%s\n%s' %
(stdout, stderr))
mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)'
- % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1]))
+ % (self.rcc.get_path(), rcc_ver.split()[-1]))
else:
mlog.log(' rcc:', mlog.red('NO'))
self.tools_detected = True
diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py
index bd8a3c4..17396ae 100644
--- a/mesonbuild/modules/rpm.py
+++ b/mesonbuild/modules/rpm.py
@@ -98,17 +98,18 @@ class RPMModule(ExtensionModule):
for dep in state.environment.coredata.deps:
fn.write('BuildRequires: pkgconfig(%s)\n' % dep[0])
for lib in state.environment.coredata.ext_libs.values():
- fn.write('BuildRequires: %s # FIXME\n' % lib.fullpath)
- mlog.warning('replace', mlog.bold(lib.fullpath), 'with real package.',
+ name = lib.get_name()
+ fn.write('BuildRequires: {} # FIXME\n'.format(name))
+ mlog.warning('replace', mlog.bold(name), 'with the real package.',
'You can use following command to find package which '
'contains this lib:',
- mlog.bold('dnf provides %s' % lib.fullpath))
+ mlog.bold("dnf provides '*/lib{}.so'".format(name)))
for prog in state.environment.coredata.ext_progs.values():
if not prog.found():
fn.write('BuildRequires: %%{_bindir}/%s # FIXME\n' %
prog.get_name())
else:
- fn.write('BuildRequires: %s\n' % ' '.join(prog.fullpath))
+ fn.write('BuildRequires: {}\n'.format(prog.get_path()))
fn.write('BuildRequires: meson\n')
fn.write('\n')
fn.write('%description\n')
diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py
index 2ffc505..a025b0c 100644
--- a/mesonbuild/scripts/meson_install.py
+++ b/mesonbuild/scripts/meson_install.py
@@ -236,12 +236,12 @@ def install_targets(d):
raise RuntimeError('File {!r} could not be found'.format(fname))
elif os.path.isfile(fname):
do_copyfile(fname, outname)
- if should_strip:
+ if should_strip and d.strip_bin is not None:
if fname.endswith('.jar'):
print('Not stripping jar target:', os.path.split(fname)[1])
continue
print('Stripping target {!r}'.format(fname))
- ps, stdo, stde = Popen_safe(['strip', outname])
+ ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
if ps.returncode != 0:
print('Could not strip file.\n')
print('Stdout:\n%s\n' % stdo)
diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py
index 7acbc76..b1efc13 100644
--- a/mesonbuild/wrap/wrap.py
+++ b/mesonbuild/wrap/wrap.py
@@ -129,7 +129,7 @@ class Resolver:
is_there = os.path.isdir(checkoutdir)
if is_there:
try:
- subprocess.check_call(['git', 'rev-parse'])
+ subprocess.check_call(['git', 'rev-parse'], cwd=checkoutdir)
is_there = True
except subprocess.CalledProcessError:
raise RuntimeError('%s is not empty but is not a valid '
diff --git a/mesontest.py b/mesontest.py
index f5da103..a1708e3 100755
--- a/mesontest.py
+++ b/mesontest.py
@@ -16,6 +16,7 @@
# A tool to run tests in many different ways.
+import shlex
import subprocess, sys, os, argparse
import pickle
from mesonbuild import build
@@ -25,6 +26,7 @@ import time, datetime, multiprocessing, json
import concurrent.futures as conc
import platform
import signal
+import random
# GNU autotools interprets a return code of 77 from tests it executes to
# mean that the test should be skipped.
@@ -60,7 +62,7 @@ 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',
help='List available tests.')
-parser.add_argument('--wrapper', default=None, dest='wrapper',
+parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split,
help='wrapper to run tests with (e.g. Valgrind)')
parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running')
@@ -82,13 +84,16 @@ parser.add_argument('-v', '--verbose', default=False, action='store_true',
help='Do not redirect stdout and stderr')
parser.add_argument('-q', '--quiet', default=False, action='store_true',
help='Produce less output to the terminal.')
-parser.add_argument('-t', '--timeout-multiplier', type=float, default=1.0,
+parser.add_argument('-t', '--timeout-multiplier', type=float, default=None,
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('--setup', default=None, dest='setup',
help='Which test setup to use.')
-parser.add_argument('args', nargs='*')
+parser.add_argument('--test-args', default=[], type=shlex.split,
+ help='Arguments to pass to the specified test(s) or all tests')
+parser.add_argument('args', nargs='*',
+ help='Optional list of tests to run')
class TestRun:
def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd,
@@ -158,7 +163,6 @@ class TestHarness:
self.skip_count = 0
self.timeout_count = 0
self.is_run = False
- self.cant_rebuild = False
self.tests = None
self.suites = None
if self.options.benchmark:
@@ -166,27 +170,6 @@ class TestHarness:
else:
self.load_datafile(os.path.join(options.wd, 'meson-private', 'meson_test_setup.dat'))
- 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):
if test.fname[0].endswith('.jar'):
cmd = ['java', '-jar'] + test.fname
@@ -210,7 +193,7 @@ class TestHarness:
stde = None
returncode = GNU_SKIP_RETURNCODE
else:
- cmd = wrap + cmd + test.cmd_args
+ cmd = wrap + cmd + test.cmd_args + self.options.test_args
starttime = time.time()
child_env = os.environ.copy()
child_env.update(self.options.global_env.get_env(child_env))
@@ -221,6 +204,14 @@ class TestHarness:
if len(test.extra_paths) > 0:
child_env['PATH'] += ';'.join([''] + test.extra_paths)
+ # If MALLOC_PERTURB_ is not set, or if it is set to an empty value,
+ # (i.e., the test or the environment don't explicitly set it), set
+ # it ourselves. We do this unconditionally because it is extremely
+ # useful to have in tests.
+ # Setting MALLOC_PERTURB_="0" will completely disable this feature.
+ if 'MALLOC_PERTURB_' not in child_env or not child_env['MALLOC_PERTURB_']:
+ child_env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
+
setsid = None
stdout = None
stderr = None
@@ -406,15 +397,18 @@ TIMEOUT: %4d
if not self.options.logbase or self.options.verbose:
return None, None, None, None
+ namebase = None
logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
- if self.options.wrapper is None:
- logfilename = logfile_base + '.txt'
- jsonlogfilename = logfile_base + '.json'
- else:
+ if self.options.wrapper:
namebase = os.path.split(self.get_wrapper()[0])[1]
- logfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.txt'
- jsonlogfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.json'
+ elif self.options.setup:
+ namebase = self.options.setup
+
+ if namebase:
+ logfile_base += '-' + namebase.replace(' ', '_')
+ logfilename = logfile_base + '.txt'
+ jsonlogfilename = logfile_base + '.json'
jsonlogfile = open(jsonlogfilename, 'w')
logfile = open(logfilename, 'w')
@@ -430,11 +424,10 @@ TIMEOUT: %4d
wrap = ['gdb', '--quiet', '--nh']
if self.options.repeat > 1:
wrap += ['-ex', 'run', '-ex', 'quit']
- elif self.options.wrapper:
- if isinstance(self.options.wrapper, str):
- wrap = self.options.wrapper.split()
- else:
- wrap = self.options.wrapper
+ # Signal the end of arguments to gdb
+ wrap += ['--args']
+ if self.options.wrapper:
+ wrap += self.options.wrapper
assert(isinstance(wrap, list))
return wrap
@@ -464,8 +457,6 @@ TIMEOUT: %4d
if self.options.gdb:
test.timeout = None
- if len(test.cmd_args):
- wrap.append('--args')
if not test.is_parallel or self.options.gdb:
self.drain_futures(futures, logfile, jsonlogfile)
@@ -510,6 +501,8 @@ TIMEOUT: %4d
if os.path.isfile('build.ninja'):
subprocess.check_call([environment.detect_ninja(), 'all'])
tests = self.get_tests()
+ if not tests:
+ return 0
self.run_tests(tests)
return self.fail_count
@@ -539,6 +532,25 @@ def merge_suite_options(options):
options.wrapper = current.exe_wrapper
return current.env
+def rebuild_all(wd):
+ if not os.path.isfile(os.path.join(wd, 'build.ninja')):
+ print("Only ninja backend is supported to rebuild tests before running them.")
+ return True
+
+ ninja = environment.detect_ninja()
+ if not ninja:
+ print("Can't find ninja, can't rebuild test.")
+ return False
+
+ p = subprocess.Popen([ninja, '-C', wd])
+ (stdo, stde) = p.communicate()
+
+ if p.returncode != 0:
+ print("Could not rebuild")
+ return False
+
+ return True
+
def run(args):
options = parser.parse_args(args)
@@ -549,6 +561,8 @@ def run(args):
global_env = merge_suite_options(options)
else:
global_env = build.EnvironmentVariables()
+ if options.timeout_multiplier is None:
+ options.timeout_multiplier = 1
setattr(options, 'global_env', global_env)
@@ -564,13 +578,14 @@ def run(args):
options.wd = os.path.abspath(options.wd)
+ if not options.no_rebuild:
+ if not rebuild_all(options.wd):
+ sys.exit(-1)
+
th = TestHarness(options)
if options.list:
list_tests(th)
return 0
- if not options.no_rebuild:
- if not th.rebuild_all():
- sys.exit(-1)
if len(options.args) == 0:
return th.doit()
return th.run_special()
diff --git a/run_tests.py b/run_tests.py
index 005717e..5025057 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2012-2016 The Meson development team
+# Copyright 2012-2017 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.
@@ -14,17 +14,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import subprocess, sys, shutil
+import os
+import sys
+import shutil
+import subprocess
import platform
from mesonbuild import mesonlib
if __name__ == '__main__':
returncode = 0
+ # Running on a developer machine? Be nice!
+ if not mesonlib.is_windows() and 'TRAVIS' not in os.environ:
+ os.nice(20)
print('Running unittests.\n')
+ units = ['InternalTests', 'AllPlatformTests']
if mesonlib.is_linux():
- returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'])
- else:
- returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v', 'InternalTests'])
+ units += ['LinuxlikeTests']
+ elif mesonlib.is_windows():
+ units += ['WindowsTests']
+ returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units)
# Ubuntu packages do not have a binary without -6 suffix.
if shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm'):
print('Running cross compilation tests.\n')
diff --git a/run_unittests.py b/run_unittests.py
index aed1412..f800d03 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright 2016 The Meson development team
+# Copyright 2016-2017 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.
@@ -18,14 +18,20 @@ import shlex
import subprocess
import re, json
import tempfile
-import pathlib
import unittest, os, sys, shutil, time
from glob import glob
+from pathlib import PurePath
import mesonbuild.compilers
import mesonbuild.environment
import mesonbuild.mesonlib
+from mesonbuild.mesonlib import is_windows, is_osx
from mesonbuild.environment import detect_ninja, Environment
-from mesonbuild.dependencies import PkgConfigDependency
+from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
+
+if is_windows():
+ exe_suffix = '.exe'
+else:
+ exe_suffix = ''
def get_soname(fname):
# HACK, fix to not use shell.
@@ -172,15 +178,169 @@ class InternalTests(unittest.TestCase):
self.assertEqual(commonpath(['blam', 'bin']), '')
prefix = '/some/path/to/prefix'
libdir = '/some/path/to/prefix/libdir'
- self.assertEqual(commonpath([prefix, libdir]), str(pathlib.PurePath(prefix)))
-
-
-class LinuxlikeTests(unittest.TestCase):
+ self.assertEqual(commonpath([prefix, libdir]), str(PurePath(prefix)))
+
+ def test_string_templates_substitution(self):
+ dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict
+ substfunc = mesonbuild.mesonlib.substitute_values
+ ME = mesonbuild.mesonlib.MesonException
+
+ # Identity
+ self.assertEqual(dictfunc([], []), {})
+
+ # One input, no outputs
+ inputs = ['bar/foo.c.in']
+ outputs = []
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
+ '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
+ # Check dictionary
+ self.assertEqual(ret, d)
+ # Check substitutions
+ cmd = ['some', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), cmd)
+ cmd = ['@INPUT@.out', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
+ cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings']
+ self.assertEqual(substfunc(cmd, d),
+ [inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:])
+ cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
+ self.assertEqual(substfunc(cmd, d),
+ inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
+ cmd = ['@OUTPUT@']
+ self.assertRaises(ME, substfunc, cmd, d)
+
+ # One input, one output
+ inputs = ['bar/foo.c.in']
+ outputs = ['out.c']
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
+ '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
+ '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
+ # Check dictionary
+ self.assertEqual(ret, d)
+ # Check substitutions
+ cmd = ['some', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), cmd)
+ cmd = ['@INPUT@.out', '@OUTPUT@', 'strings']
+ self.assertEqual(substfunc(cmd, d),
+ [inputs[0] + '.out'] + outputs + cmd[2:])
+ cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@']
+ self.assertEqual(substfunc(cmd, d),
+ [inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs)
+ cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
+ self.assertEqual(substfunc(cmd, d),
+ inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
+
+ # One input, one output with a subdir
+ outputs = ['dir/out.c']
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
+ '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
+ '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
+ # Check dictionary
+ self.assertEqual(ret, d)
+
+ # Two inputs, no outputs
+ inputs = ['bar/foo.c.in', 'baz/foo.c.in']
+ outputs = []
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]}
+ # Check dictionary
+ self.assertEqual(ret, d)
+ # Check substitutions
+ cmd = ['some', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), cmd)
+ cmd = ['@INPUT@', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), inputs + cmd[1:])
+ cmd = ['@INPUT0@.out', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
+ cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings']
+ self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
+ cmd = ['@INPUT0@', '@INPUT1@', 'strings']
+ self.assertEqual(substfunc(cmd, d), inputs + cmd[2:])
+ # Many inputs, can't use @INPUT@ like this
+ cmd = ['@INPUT@.out', 'ordinary', 'strings']
+ # Not enough inputs
+ cmd = ['@INPUT2@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+ # Too many inputs
+ cmd = ['@PLAINNAME@']
+ self.assertRaises(ME, substfunc, cmd, d)
+ cmd = ['@BASENAME@']
+ self.assertRaises(ME, substfunc, cmd, d)
+ # No outputs
+ cmd = ['@OUTPUT@']
+ self.assertRaises(ME, substfunc, cmd, d)
+ cmd = ['@OUTPUT0@']
+ self.assertRaises(ME, substfunc, cmd, d)
+ cmd = ['@OUTDIR@']
+ self.assertRaises(ME, substfunc, cmd, d)
+
+ # Two inputs, one output
+ outputs = ['dir/out.c']
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
+ '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
+ # Check dictionary
+ self.assertEqual(ret, d)
+ # Check substitutions
+ cmd = ['some', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), cmd)
+ cmd = ['@OUTPUT@', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
+ cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:])
+ cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings']
+ self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
+ # Many inputs, can't use @INPUT@ like this
+ cmd = ['@INPUT@.out', 'ordinary', 'strings']
+ # Not enough inputs
+ cmd = ['@INPUT2@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+ # Not enough outputs
+ cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+
+ # Two inputs, two outputs
+ outputs = ['dir/out.c', 'dir/out2.c']
+ ret = dictfunc(inputs, outputs)
+ d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
+ '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
+ '@OUTDIR@': 'dir'}
+ # Check dictionary
+ self.assertEqual(ret, d)
+ # Check substitutions
+ cmd = ['some', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), cmd)
+ cmd = ['@OUTPUT@', 'ordinary', 'strings']
+ self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
+ cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
+ self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
+ cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
+ self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
+ # Many inputs, can't use @INPUT@ like this
+ cmd = ['@INPUT@.out', 'ordinary', 'strings']
+ # Not enough inputs
+ cmd = ['@INPUT2@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+ # Not enough outputs
+ cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+ # Many outputs, can't use @OUTPUT@ like this
+ cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
+ self.assertRaises(ME, substfunc, cmd, d)
+
+
+class BasePlatformTests(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.src_root = src_root
+ # In case the directory is inside a symlinked directory, find the real
+ # path otherwise we might not find the srcdir from inside the builddir.
+ self.builddir = os.path.realpath(tempfile.mkdtemp())
self.logdir = os.path.join(self.builddir, 'meson-logs')
self.prefix = '/usr'
self.libdir = os.path.join(self.prefix, 'lib')
@@ -194,7 +354,6 @@ class LinuxlikeTests(unittest.TestCase):
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()
def tearDown(self):
@@ -203,20 +362,26 @@ class LinuxlikeTests(unittest.TestCase):
super().tearDown()
def _run(self, command):
- self.output += subprocess.check_output(command, stderr=subprocess.STDOUT,
- env=os.environ.copy())
+ output = subprocess.check_output(command, stderr=subprocess.STDOUT,
+ env=os.environ.copy(),
+ universal_newlines=True)
+ print(output)
+ return output
- def init(self, srcdir, extra_args=None):
+ def init(self, srcdir, extra_args=None, default_args=True):
if extra_args is None:
extra_args = []
- args = [srcdir, self.builddir,
- '--prefix', self.prefix,
- '--libdir', self.libdir]
+ args = [srcdir, self.builddir]
+ if default_args:
+ args += ['--prefix', self.prefix,
+ '--libdir', self.libdir]
self._run(self.meson_command + args + extra_args)
self.privatedir = os.path.join(self.builddir, 'meson-private')
- def build(self):
- self._run(self.ninja_command)
+ def build(self, extra_args=None):
+ if extra_args is None:
+ extra_args = []
+ self._run(self.ninja_command + extra_args)
def run_tests(self):
self._run(self.ninja_command + ['test'])
@@ -229,9 +394,18 @@ class LinuxlikeTests(unittest.TestCase):
self._run(self.ninja_command + ['uninstall'])
def run_target(self, target):
- self.output += subprocess.check_output(self.ninja_command + [target])
+ output = subprocess.check_output(self.ninja_command + [target],
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ print(output)
+ return output
- def setconf(self, arg):
+ def setconf(self, arg, will_build=True):
+ # This is needed to increase the difference between build.ninja's
+ # timestamp and coredata.dat's timestamp due to a Ninja bug.
+ # https://github.com/ninja-build/ninja/issues/371
+ if will_build:
+ time.sleep(1)
self._run(self.mconf_command + [arg, self.builddir])
def wipe(self):
@@ -260,6 +434,447 @@ class LinuxlikeTests(unittest.TestCase):
universal_newlines=True)
return json.loads(out)
+ def assertPathEqual(self, path1, path2):
+ '''
+ Handles a lot of platform-specific quirks related to paths such as
+ separator, case-sensitivity, etc.
+ '''
+ self.assertEqual(PurePath(path1), PurePath(path2))
+
+ def assertPathBasenameEqual(self, path, basename):
+ msg = '{!r} does not end with {!r}'.format(path, basename)
+ # We cannot use os.path.basename because it returns '' when the path
+ # ends with '/' for some silly reason. This is not how the UNIX utility
+ # `basename` works.
+ path_basename = PurePath(path).parts[-1]
+ self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
+
+
+class AllPlatformTests(BasePlatformTests):
+ '''
+ Tests that should run on all platforms
+ '''
+ def test_default_options_prefix(self):
+ '''
+ Tests that setting a prefix in default_options in project() works.
+ Can't be an ordinary test because we pass --prefix to meson there.
+ https://github.com/mesonbuild/meson/issues/1349
+ '''
+ testdir = os.path.join(self.common_test_dir, '94 default options')
+ self.init(testdir, default_args=False)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ if opt['name'] == 'prefix':
+ prefix = opt['value']
+ self.assertEqual(prefix, '/absoluteprefix')
+
+ def test_absolute_prefix_libdir(self):
+ '''
+ Tests that setting absolute paths for --prefix and --libdir work. Can't
+ be an ordinary test because these are set via the command-line.
+ https://github.com/mesonbuild/meson/issues/1341
+ https://github.com/mesonbuild/meson/issues/1345
+ '''
+ testdir = os.path.join(self.common_test_dir, '94 default options')
+ prefix = '/someabs'
+ libdir = 'libdir'
+ extra_args = ['--prefix=' + prefix,
+ # This can just be a relative path, but we want to test
+ # that passing this as an absolute path also works
+ '--libdir=' + prefix + '/' + libdir]
+ self.init(testdir, extra_args, default_args=False)
+ opts = self.introspect('--buildoptions')
+ for opt in opts:
+ if opt['name'] == 'prefix':
+ self.assertEqual(prefix, opt['value'])
+ elif opt['name'] == 'libdir':
+ self.assertEqual(libdir, opt['value'])
+
+ def test_libdir_must_be_inside_prefix(self):
+ '''
+ Tests that libdir is forced to be inside prefix no matter how it is set.
+ Must be a unit test for obvious reasons.
+ '''
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+ # libdir being inside prefix is ok
+ args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
+ self.init(testdir, args)
+ self.wipe()
+ # libdir not being inside prefix is not ok
+ args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
+ self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
+ self.wipe()
+ # libdir must be inside prefix even when set via mesonconf
+ self.init(testdir)
+ self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False)
+
+ def test_static_library_overwrite(self):
+ '''
+ Tests that static libraries are never appended to, always overwritten.
+ Has to be a unit test because this involves building a project,
+ reconfiguring, and building it again so that `ar` is run twice on the
+ same static library.
+ https://github.com/mesonbuild/meson/issues/1355
+ '''
+ testdir = os.path.join(self.common_test_dir, '3 static')
+ env = Environment(testdir, self.builddir, self.meson_command,
+ get_fake_options(self.prefix), [])
+ cc = env.detect_c_compiler(False)
+ static_linker = env.detect_static_linker(cc)
+ if not isinstance(static_linker, mesonbuild.compilers.ArLinker):
+ raise unittest.SkipTest('static linker is not `ar`')
+ # Configure
+ self.init(testdir)
+ # Get name of static library
+ targets = self.introspect('--targets')
+ self.assertEqual(len(targets), 1)
+ libname = targets[0]['filename']
+ # Build and get contents of static library
+ self.build()
+ before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
+ # Filter out non-object-file contents
+ before = [f for f in before if f.endswith(('.o', '.obj'))]
+ # Static library should contain only one object
+ self.assertEqual(len(before), 1, msg=before)
+ # Change the source to be built into the static library
+ self.setconf('-Dsource=libfile2.c')
+ self.build()
+ after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
+ # Filter out non-object-file contents
+ after = [f for f in after if f.endswith(('.o', '.obj'))]
+ # Static library should contain only one object
+ self.assertEqual(len(after), 1, msg=after)
+ # and the object must have changed
+ self.assertNotEqual(before, after)
+
+ 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()
+ # Rules will get written out in this order
+ self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
+ self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
+ self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
+ self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
+ # FIXME: We don't have access to the linker command
+
+ 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_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')
+ if intro[0]['type'] == 'executable':
+ intro = intro[::-1]
+ self.assertPathEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
+ self.assertPathEqual(intro[1]['install_filename'], '/usr/bin/prog' + exe_suffix)
+
+ def test_uninstall(self):
+ exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
+ testdir = os.path.join(self.common_test_dir, '8 install')
+ self.init(testdir)
+ self.assertFalse(os.path.exists(exename))
+ self.install()
+ self.assertTrue(os.path.exists(exename))
+ self.uninstall()
+ self.assertFalse(os.path.exists(exename))
+
+ def test_testsetups(self):
+ if not shutil.which('valgrind'):
+ raise unittest.SkipTest('Valgrind not installed.')
+ testdir = os.path.join(self.unit_test_dir, '2 testsetups')
+ self.init(testdir)
+ self.build()
+ # Run tests without setup
+ self.run_tests()
+ with open(os.path.join(self.logdir, 'testlog.txt')) as f:
+ basic_log = f.read()
+ # Run buggy test with setup that has env that will make it fail
+ self.assertRaises(subprocess.CalledProcessError,
+ self._run, self.mtest_command + ['--setup=valgrind'])
+ with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
+ vg_log = f.read()
+ self.assertFalse('TEST_ENV is set' in basic_log)
+ self.assertFalse('Memcheck' in basic_log)
+ self.assertTrue('TEST_ENV is set' in vg_log)
+ self.assertTrue('Memcheck' in vg_log)
+ # Run buggy test with setup without env that will pass
+ self._run(self.mtest_command + ['--setup=wrapper'])
+ # Setup with no properties works
+ self._run(self.mtest_command + ['--setup=empty'])
+ # Setup with only env works
+ self._run(self.mtest_command + ['--setup=onlyenv'])
+ # Setup with only a timeout works
+ self._run(self.mtest_command + ['--setup=timeout'])
+
+ def assertFailedTestCount(self, failure_count, command):
+ try:
+ self._run(command)
+ self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
+ except subprocess.CalledProcessError as e:
+ self.assertEqual(e.returncode, failure_count)
+
+ def test_suite_selection(self):
+ testdir = os.path.join(self.unit_test_dir, '4 suite selection')
+ self.init(testdir)
+ self.build()
+
+ self.assertFailedTestCount(3, self.mtest_command)
+
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
+
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
+
+ self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
+ self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
+ self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
+
+ self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
+
+ def test_build_by_default(self):
+ testdir = os.path.join(self.common_test_dir, '137 build by default')
+ self.init(testdir)
+ self.build()
+ genfile = os.path.join(self.builddir, 'generated.dat')
+ exe = os.path.join(self.builddir, 'fooprog' + exe_suffix)
+ self.assertTrue(os.path.exists(genfile))
+ self.assertFalse(os.path.exists(exe))
+ self._run(self.ninja_command + ['fooprog' + exe_suffix])
+ self.assertTrue(os.path.exists(exe))
+
+ def test_internal_include_order(self):
+ testdir = os.path.join(self.common_test_dir, '138 include order')
+ self.init(testdir)
+ for cmd in self.get_compdb():
+ if cmd['file'].endswith('/main.c'):
+ cmd = cmd['command']
+ break
+ else:
+ raise Exception('Could not find main.c command')
+ if cmd.endswith('.rsp'):
+ # Pretend to build so that the rsp files are generated
+ self.build(['-d', 'keeprsp', '-n'])
+ # Extract the actual command from the rsp file
+ rsp = os.path.join(self.builddir, cmd.split('cl @')[1])
+ with open(rsp, 'r', encoding='utf-8') as f:
+ cmd = f.read()
+ incs = [a for a in shlex.split(cmd) if a.startswith("-I")]
+ self.assertEqual(len(incs), 8)
+ # target private dir
+ self.assertPathEqual(incs[0], "-Isub4/someexe@exe")
+ # target build subdir
+ self.assertPathEqual(incs[1], "-Isub4")
+ # target source subdir
+ self.assertPathBasenameEqual(incs[2], 'sub4')
+ # include paths added via per-target c_args: ['-I'...]
+ self.assertPathBasenameEqual(incs[3], 'sub3')
+ # target include_directories: build dir
+ self.assertPathEqual(incs[4], "-Isub2")
+ # target include_directories: source dir
+ self.assertPathBasenameEqual(incs[5], 'sub2')
+ # target internal dependency include_directories: build dir
+ self.assertPathEqual(incs[6], "-Isub1")
+ # target internal dependency include_directories: source dir
+ self.assertPathBasenameEqual(incs[7], 'sub1')
+
+ def test_compiler_detection(self):
+ '''
+ Test that automatic compiler detection and setting from the environment
+ both work just fine. This is needed because while running project tests
+ and other unit tests, we always read CC/CXX/etc from the environment.
+ '''
+ gnu = mesonbuild.compilers.GnuCompiler
+ clang = mesonbuild.compilers.ClangCompiler
+ intel = mesonbuild.compilers.IntelCompiler
+ msvc = mesonbuild.compilers.VisualStudioCCompiler
+ ar = mesonbuild.compilers.ArLinker
+ lib = mesonbuild.compilers.VisualStudioLinker
+ langs = [('c', 'CC'), ('cpp', 'CXX')]
+ if not is_windows():
+ langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
+ testdir = os.path.join(self.unit_test_dir, '5 compiler detection')
+ env = Environment(testdir, self.builddir, self.meson_command,
+ get_fake_options(self.prefix), [])
+ for lang, evar in langs:
+ evalue = None
+ # Detect with evar and do sanity checks on that
+ if evar in os.environ:
+ ecc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
+ elinker = env.detect_static_linker(ecc)
+ # Pop it so we don't use it for the next detection
+ evalue = os.environ.pop(evar)
+ # Very rough/strict heuristics. Would never work for actual
+ # compiler detection, but should be ok for the tests.
+ if os.path.basename(evalue).startswith('g'):
+ self.assertIsInstance(ecc, gnu)
+ self.assertIsInstance(elinker, ar)
+ elif 'clang' in os.path.basename(evalue):
+ self.assertIsInstance(ecc, clang)
+ self.assertIsInstance(elinker, ar)
+ elif os.path.basename(evalue).startswith('ic'):
+ self.assertIsInstance(ecc, intel)
+ self.assertIsInstance(elinker, ar)
+ elif os.path.basename(evalue).startswith('cl'):
+ self.assertIsInstance(ecc, msvc)
+ self.assertIsInstance(elinker, lib)
+ else:
+ raise AssertionError('Unknown compiler {!r}'.format(evalue))
+ # Check that we actually used the evalue correctly as the compiler
+ self.assertEqual(ecc.get_exelist(), shlex.split(evalue))
+ # Do auto-detection of compiler based on platform, PATH, etc.
+ cc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
+ linker = env.detect_static_linker(cc)
+ # Check compiler type
+ if isinstance(cc, gnu):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_OSX)
+ elif is_windows():
+ self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_MINGW)
+ else:
+ self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_STANDARD)
+ if isinstance(cc, clang):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_OSX)
+ elif is_windows():
+ # Not implemented yet
+ self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_WIN)
+ else:
+ self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_STANDARD)
+ if isinstance(cc, intel):
+ self.assertIsInstance(linker, ar)
+ if is_osx():
+ self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_OSX)
+ elif is_windows():
+ self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_WIN)
+ else:
+ self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_STANDARD)
+ if isinstance(cc, msvc):
+ self.assertTrue(is_windows())
+ self.assertIsInstance(linker, lib)
+ self.assertEqual(cc.id, 'msvc')
+ # Set evar ourselves to a wrapper script that just calls the same
+ # exelist + some argument. This is meant to test that setting
+ # something like `ccache gcc -pipe` or `distcc ccache gcc` works.
+ wrapper = os.path.join(testdir, 'compiler wrapper.py')
+ wrappercc = [sys.executable, wrapper] + cc.get_exelist() + cc.get_always_args()
+ wrappercc_s = ''
+ for w in wrappercc:
+ wrappercc_s += shlex.quote(w) + ' '
+ os.environ[evar] = wrappercc_s
+ wcc = getattr(env, 'detect_{}_compiler'.format(lang))(False)
+ # Check static linker too
+ wrapperlinker = [sys.executable, wrapper] + linker.get_exelist() + linker.get_always_args()
+ wrapperlinker_s = ''
+ for w in wrapperlinker:
+ wrapperlinker_s += shlex.quote(w) + ' '
+ os.environ['AR'] = wrapperlinker_s
+ wlinker = env.detect_static_linker(wcc)
+ # Must be the same type since it's a wrapper around the same exelist
+ self.assertIs(type(cc), type(wcc))
+ self.assertIs(type(linker), type(wlinker))
+ # Ensure that the exelist is correct
+ self.assertEqual(wcc.get_exelist(), wrappercc)
+ self.assertEqual(wlinker.get_exelist(), wrapperlinker)
+
+
+class WindowsTests(BasePlatformTests):
+ '''
+ Tests that should run on Cygwin, MinGW, and MSVC
+ '''
+ def setUp(self):
+ super().setUp()
+ self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows')
+
+ def test_find_program(self):
+ '''
+ Test that Windows-specific edge-cases in find_program are functioning
+ correctly. Cannot be an ordinary test because it involves manipulating
+ PATH to point to a directory with Python scripts.
+ '''
+ testdir = os.path.join(self.platform_test_dir, '9 find program')
+ # Find `cmd` and `cmd.exe`
+ prog1 = ExternalProgram('cmd')
+ self.assertTrue(prog1.found(), msg='cmd not found')
+ prog2 = ExternalProgram('cmd.exe')
+ self.assertTrue(prog2.found(), msg='cmd.exe not found')
+ self.assertPathEqual(prog1.get_path(), prog2.get_path())
+ # Find cmd with an absolute path that's missing the extension
+ cmd_path = prog2.get_path()[:-4]
+ prog = ExternalProgram(cmd_path)
+ self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path))
+ # Finding a script with no extension inside a directory works
+ prog = ExternalProgram(os.path.join(testdir, 'test-script'))
+ self.assertTrue(prog.found(), msg='test-script not found')
+ # Finding a script with an extension inside a directory works
+ prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py'))
+ self.assertTrue(prog.found(), msg='test-script-ext.py not found')
+ # Finding a script in PATH w/o extension works and adds the interpreter
+ os.environ['PATH'] += os.pathsep + testdir
+ prog = ExternalProgram('test-script-ext')
+ self.assertTrue(prog.found(), msg='test-script-ext not found in PATH')
+ self.assertPathEqual(prog.get_command()[0], sys.executable)
+ self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
+ # Finding a script in PATH with extension works and adds the interpreter
+ prog = ExternalProgram('test-script-ext.py')
+ self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH')
+ self.assertPathEqual(prog.get_command()[0], sys.executable)
+ self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
+
+
+class LinuxlikeTests(BasePlatformTests):
+ '''
+ Tests that should run on Linux and *BSD
+ '''
def test_basic_soname(self):
'''
Test that the soname is set correctly for shared libraries. This can't
@@ -298,10 +913,6 @@ class LinuxlikeTests(unittest.TestCase):
self.init(testdir)
compdb = self.get_compdb()
self.assertIn('-fPIC', compdb[0]['command'])
- # This is needed to increase the difference between build.ninja's
- # timestamp and coredata.dat's timestamp due to a Ninja bug.
- # https://github.com/ninja-build/ninja/issues/371
- time.sleep(1)
self.setconf('-Db_staticpic=false')
# Regenerate build
self.build()
@@ -358,45 +969,6 @@ class LinuxlikeTests(unittest.TestCase):
self.assertIn("'-Werror'", vala_command)
self.assertIn("'-Werror'", 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()
- # Rules will get written out in this order
- self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
- self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
- self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
- self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
- # 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')
- if intro[0]['type'] == 'executable':
- intro = intro[::-1]
- self.assertEqual(intro[0]['install_filename'], '/usr/lib/libstat.a')
- self.assertEqual(intro[1]['install_filename'], '/usr/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
@@ -500,16 +1072,6 @@ class LinuxlikeTests(unittest.TestCase):
Oargs = [arg for arg in cmd if arg.startswith('-O')]
self.assertEqual(Oargs, [Oflag, '-O0'])
- def test_uninstall(self):
- exename = os.path.join(self.installdir, 'usr/bin/prog')
- testdir = os.path.join(self.common_test_dir, '8 install')
- self.init(testdir)
- self.assertFalse(os.path.exists(exename))
- self.install()
- self.assertTrue(os.path.exists(exename))
- self.uninstall()
- self.assertFalse(os.path.exists(exename))
-
def test_custom_target_exe_data_deterministic(self):
testdir = os.path.join(self.common_test_dir, '117 custom target capture')
self.init(testdir)
@@ -519,79 +1081,6 @@ class LinuxlikeTests(unittest.TestCase):
meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
- def test_testsetups(self):
- if not shutil.which('valgrind'):
- raise unittest.SkipTest('Valgrind not installed.')
- testdir = os.path.join(self.unit_test_dir, '2 testsetups')
- self.init(testdir)
- self.build()
- self.run_tests()
- with open(os.path.join(self.logdir, 'testlog.txt')) as f:
- basic_log = f.read()
- self.assertRaises(subprocess.CalledProcessError,
- self._run, self.mtest_command + ['--setup=valgrind'])
- with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
- vg_log = f.read()
- self.assertFalse('TEST_ENV is set' in basic_log)
- self.assertFalse('Memcheck' in basic_log)
- self.assertTrue('TEST_ENV is set' in vg_log)
- self.assertTrue('Memcheck' in vg_log)
-
- def assertFailedTestCount(self, failure_count, command):
- try:
- self._run(command)
- self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
- except subprocess.CalledProcessError as e:
- self.assertEqual(e.returncode, failure_count)
-
- def test_suite_selection(self):
- testdir = os.path.join(self.unit_test_dir, '4 suite selection')
- self.init(testdir)
- self.build()
-
- self.assertFailedTestCount(3, self.mtest_command)
-
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
- self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success'])
- self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail'])
-
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix'])
-
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success'])
-
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success'])
-
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
-
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
- self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
- self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success'])
-
- self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
- self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
- self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
- self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
-
- self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
-
def _test_stds_impl(self, testdir, compiler, p):
lang_std = p + '_std'
# Check that all the listed -std=xxx options for this compiler work
@@ -644,31 +1133,6 @@ class LinuxlikeTests(unittest.TestCase):
cpp = env.detect_cpp_compiler(False)
self._test_stds_impl(testdir, cpp, 'cpp')
- def test_build_by_default(self):
- testdir = os.path.join(self.common_test_dir, '137 build by default')
- self.init(testdir)
- self.build()
- genfile = os.path.join(self.builddir, 'generated.dat')
- exe = os.path.join(self.builddir, 'fooprog')
- self.assertTrue(os.path.exists(genfile))
- self.assertFalse(os.path.exists(exe))
- self._run(self.ninja_command + ['fooprog'])
- self.assertTrue(os.path.exists(exe))
-
- def test_libdir_must_be_inside_prefix(self):
- testdir = os.path.join(self.common_test_dir, '1 trivial')
- # libdir being inside prefix is ok
- args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
- self.init(testdir, args)
- self.wipe()
- # libdir not being inside prefix is not ok
- args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
- self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
- self.wipe()
- # libdir must be inside prefix even when set via mesonconf
- self.init(testdir)
- self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt')
-
def test_installed_modes(self):
'''
Test that files installed by these tests have the correct permissions.
@@ -722,47 +1186,15 @@ class LinuxlikeTests(unittest.TestCase):
# The chown failed nonfatally if we're not root
self.assertEqual(0, statf.st_uid)
- def test_internal_include_order(self):
- testdir = os.path.join(self.common_test_dir, '138 include order')
- self.init(testdir)
- for cmd in self.get_compdb():
- if cmd['file'].endswith('/main.c'):
- cmd = cmd['command']
- break
- else:
- raise Exception('Could not find main.c command')
- incs = [a for a in shlex.split(cmd) if a.startswith("-I")]
- self.assertEqual(len(incs), 8)
- # target private dir
- self.assertEqual(incs[0], "-Isub4/someexe@exe")
- # target build subdir
- self.assertEqual(incs[1], "-Isub4")
- # target source subdir
- msg = "{!r} does not end with '/sub4'".format(incs[2])
- self.assertTrue(incs[2].endswith("/sub4"), msg)
- # include paths added via per-target c_args: ['-I'...]
- msg = "{!r} does not end with '/sub3'".format(incs[3])
- self.assertTrue(incs[3].endswith("/sub3"), msg)
- # target include_directories: build dir
- self.assertEqual(incs[4], "-Isub2")
- # target include_directories: source dir
- msg = "{!r} does not end with '/sub2'".format(incs[5])
- self.assertTrue(incs[5].endswith("/sub2"), msg)
- # target internal dependency include_directories: build dir
- self.assertEqual(incs[6], "-Isub1")
- # target internal dependency include_directories: source dir
- msg = "{!r} does not end with '/sub1'".format(incs[7])
- self.assertTrue(incs[7].endswith("/sub1"), msg)
-
class RewriterTests(unittest.TestCase):
def setUp(self):
super().setUp()
src_root = os.path.dirname(__file__)
- self.testroot = tempfile.mkdtemp()
+ self.testroot = os.path.realpath(tempfile.mkdtemp())
self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')]
- self.tmpdir = tempfile.mkdtemp()
+ self.tmpdir = os.path.realpath(tempfile.mkdtemp())
self.workdir = os.path.join(self.tmpdir, 'foo')
self.test_dir = os.path.join(src_root, 'test cases/rewrite')
@@ -785,34 +1217,38 @@ class RewriterTests(unittest.TestCase):
def test_basic(self):
self.prime('1 basic')
- subprocess.check_output(self.rewrite_command + ['remove',
- '--target=trivialprog',
- '--filename=notthere.c',
- '--sourcedir', self.workdir])
+ subprocess.check_call(self.rewrite_command + ['remove',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir],
+ universal_newlines=True)
self.check_effectively_same('meson.build', 'removed.txt')
- subprocess.check_output(self.rewrite_command + ['add',
- '--target=trivialprog',
- '--filename=notthere.c',
- '--sourcedir', self.workdir])
+ subprocess.check_call(self.rewrite_command + ['add',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir],
+ universal_newlines=True)
self.check_effectively_same('meson.build', 'added.txt')
- subprocess.check_output(self.rewrite_command + ['remove',
- '--target=trivialprog',
- '--filename=notthere.c',
- '--sourcedir', self.workdir])
+ subprocess.check_call(self.rewrite_command + ['remove',
+ '--target=trivialprog',
+ '--filename=notthere.c',
+ '--sourcedir', self.workdir],
+ universal_newlines=True)
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])
+ subprocess.check_call(self.rewrite_command + ['remove',
+ '--target=something',
+ '--filename=second.c',
+ '--sourcedir', self.workdir],
+ universal_newlines=True)
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()
+ unittest.main(buffer=True)
diff --git a/syntax-highlighting/vim/syntax/meson.vim b/syntax-highlighting/vim/syntax/meson.vim
index 0799237..49921c1 100644
--- a/syntax-highlighting/vim/syntax/meson.vim
+++ b/syntax-highlighting/vim/syntax/meson.vim
@@ -67,8 +67,8 @@ syn keyword mesonBuiltin
\ add_global_link_arguments
\ add_languages
\ add_project_arguments
- \ add_project_arguments
\ add_project_link_arguments
+ \ add_test_setup
\ benchmark
\ build_machine
\ build_target
@@ -94,7 +94,6 @@ syn keyword mesonBuiltin
\ install_headers
\ install_man
\ install_subdir
- \ is_subproject
\ is_variable
\ jar
\ join_paths
diff --git a/test cases/common/105 find program path/meson.build b/test cases/common/105 find program path/meson.build
index ba6030b..e1e6d2e 100644
--- a/test cases/common/105 find program path/meson.build
+++ b/test cases/common/105 find program path/meson.build
@@ -1,10 +1,25 @@
project('find program', 'c')
-prog = find_program('program.py')
-
python = find_program('python3', required : false)
if not python.found()
python = find_program('python')
endif
-run_command(python, prog.path())
+# Source file via string
+prog = find_program('program.py')
+# Source file via files()
+progf = files('program.py')
+# Built file
+py = configure_file(input : 'program.py',
+ output : 'builtprogram.py',
+ configuration : configuration_data())
+
+foreach f : [prog, find_program(py), find_program(progf)]
+ ret = run_command(python, f.path())
+ assert(ret.returncode() == 0, 'can\'t manually run @0@'.format(prog.path()))
+ assert(ret.stdout().strip() == 'Found', 'wrong output from manually-run @0@'.format(prog.path()))
+
+ ret = run_command(f)
+ assert(ret.returncode() == 0, 'can\'t run @0@'.format(prog.path()))
+ assert(ret.stdout().strip() == 'Found', 'wrong output from @0@'.format(prog.path()))
+endforeach
diff --git a/test cases/common/105 find program path/program.py b/test cases/common/105 find program path/program.py
index b910718..2ebc564 100644
--- a/test cases/common/105 find program path/program.py
+++ b/test cases/common/105 find program path/program.py
@@ -1,3 +1,3 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
print("Found")
diff --git a/test cases/common/107 postconf/postconf.py b/test cases/common/107 postconf/postconf.py
index 9a23cfa..950c706 100644
--- a/test cases/common/107 postconf/postconf.py
+++ b/test cases/common/107 postconf/postconf.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
diff --git a/test cases/common/108 postconf with args/postconf.py b/test cases/common/108 postconf with args/postconf.py
index 3ed0450..cef7f79 100644
--- a/test cases/common/108 postconf with args/postconf.py
+++ b/test cases/common/108 postconf with args/postconf.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os
diff --git a/test cases/common/113 generatorcustom/catter.py b/test cases/common/113 generatorcustom/catter.py
index a79b739..198fa98 100755
--- a/test cases/common/113 generatorcustom/catter.py
+++ b/test cases/common/113 generatorcustom/catter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/113 generatorcustom/gen.py b/test cases/common/113 generatorcustom/gen.py
index f9efb47..c1e34ed 100755
--- a/test cases/common/113 generatorcustom/gen.py
+++ b/test cases/common/113 generatorcustom/gen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/118 allgenerate/converter.py b/test cases/common/118 allgenerate/converter.py
index cc2c574..f8e2ca0 100755
--- a/test cases/common/118 allgenerate/converter.py
+++ b/test cases/common/118 allgenerate/converter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/118 allgenerate/meson.build b/test cases/common/118 allgenerate/meson.build
index 36abbe9..049e849 100644
--- a/test cases/common/118 allgenerate/meson.build
+++ b/test cases/common/118 allgenerate/meson.build
@@ -13,7 +13,7 @@ c = g.process('foobar.cpp.in')
prog = executable('genexe', c)
c2 = custom_target('c2gen',
- output : 'c2gen.cpp',
+ output : '@BASENAME@',
input : 'foobar.cpp.in',
command : [comp, '@INPUT@', '@OUTPUT@'])
diff --git a/test cases/common/119 pathjoin/meson.build b/test cases/common/119 pathjoin/meson.build
index 7f33791..751ca68 100644
--- a/test cases/common/119 pathjoin/meson.build
+++ b/test cases/common/119 pathjoin/meson.build
@@ -1,8 +1,17 @@
project('pathjoin', 'c')
+# Test string-args form since that is the canonical way
assert(join_paths('foo') == 'foo', 'Single argument join is broken')
assert(join_paths('foo', 'bar') == 'foo/bar', 'Path joining is broken')
assert(join_paths('foo', 'bar', 'baz') == 'foo/bar/baz', 'Path joining is broken')
assert(join_paths('/foo', 'bar') == '/foo/bar', 'Path joining is broken')
assert(join_paths('foo', '/bar') == '/bar', 'Absolute path joining is broken')
assert(join_paths('/foo', '/bar') == '/bar', 'Absolute path joining is broken')
+
+# Test array form since people are using that too
+assert(join_paths(['foo']) == 'foo', 'Single argument join is broken')
+assert(join_paths(['foo', 'bar']) == 'foo/bar', 'Path joining is broken')
+assert(join_paths(['foo', 'bar', 'baz']) == 'foo/bar/baz', 'Path joining is broken')
+assert(join_paths(['/foo', 'bar']) == '/foo/bar', 'Path joining is broken')
+assert(join_paths(['foo', '/bar']) == '/bar', 'Absolute path joining is broken')
+assert(join_paths(['/foo', '/bar']) == '/bar', 'Absolute path joining is broken')
diff --git a/test cases/common/129 object only target/meson.build b/test cases/common/129 object only target/meson.build
index 58d01d9..d83a658 100644
--- a/test cases/common/129 object only target/meson.build
+++ b/test cases/common/129 object only target/meson.build
@@ -16,7 +16,7 @@ cc = meson.get_compiler('c').cmd_array().get(-1)
# provided by the source tree
source1 = configure_file(input : 'source.c',
output : 'source' + ext,
- command : [comp, cc, 'source.c',
+ command : [comp, cc, files('source.c'),
join_paths(meson.current_build_dir(), 'source' + ext)])
obj = static_library('obj', objects : source1)
diff --git a/test cases/common/129 object only target/obj_generator.py b/test cases/common/129 object only target/obj_generator.py
index f0239b4..a33872a 100755
--- a/test cases/common/129 object only target/obj_generator.py
+++ b/test cases/common/129 object only target/obj_generator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Mimic a binary that generates an object file (e.g. windres).
diff --git a/test cases/common/131 custom target directory install/docgen.py b/test cases/common/131 custom target directory install/docgen.py
index 4d80124..245f370 100644
--- a/test cases/common/131 custom target directory install/docgen.py
+++ b/test cases/common/131 custom target directory install/docgen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
import sys
diff --git a/test cases/common/133 configure file in generator/src/gen.py b/test cases/common/133 configure file in generator/src/gen.py
index 5bccece..99b7cdd 100755
--- a/test cases/common/133 configure file in generator/src/gen.py
+++ b/test cases/common/133 configure file in generator/src/gen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/134 generated llvm ir/copyfile.py b/test cases/common/134 generated llvm ir/copyfile.py
index da503e2..ff42ac3 100644
--- a/test cases/common/134 generated llvm ir/copyfile.py
+++ b/test cases/common/134 generated llvm ir/copyfile.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
import shutil
diff --git a/test cases/common/135 generated assembly/copyfile.py b/test cases/common/135 generated assembly/copyfile.py
index da503e2..ff42ac3 100644
--- a/test cases/common/135 generated assembly/copyfile.py
+++ b/test cases/common/135 generated assembly/copyfile.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
import shutil
diff --git a/test cases/common/16 configure file/check_file.py b/test cases/common/16 configure file/check_file.py
new file mode 100644
index 0000000..449b77a
--- /dev/null
+++ b/test cases/common/16 configure file/check_file.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+assert(os.path.exists(sys.argv[1]))
diff --git a/test cases/common/16 configure file/generator.py b/test cases/common/16 configure file/generator.py
index 2c7f2f8..e3cc881 100755
--- a/test cases/common/16 configure file/generator.py
+++ b/test cases/common/16 configure file/generator.py
@@ -1,15 +1,17 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
-# On some platforms "python" points to Python 2
-# on others to Python 3. Work with both.
-
-from __future__ import print_function
import sys, os
+from pathlib import Path
if len(sys.argv) != 3:
print("Wrong amount of parameters.")
-assert(os.path.exists(sys.argv[1]))
+build_dir = Path(os.environ['MESON_BUILD_ROOT'])
+subdir = Path(os.environ['MESON_SUBDIR'])
+inputf = Path(sys.argv[1])
+outputf = Path(sys.argv[2])
+
+assert(inputf.exists())
-with open(sys.argv[2], 'w') as ofile:
+with outputf.open('w') as ofile:
ofile.write("#define ZERO_RESULT 0\n")
diff --git a/test cases/common/16 configure file/installed_files.txt b/test cases/common/16 configure file/installed_files.txt
index 219b4c0..d9fee12 100644
--- a/test cases/common/16 configure file/installed_files.txt
+++ b/test cases/common/16 configure file/installed_files.txt
@@ -1 +1,3 @@
usr/share/appdir/config2.h
+usr/share/appdireh/config2-1.h
+usr/share/appdirok/config2-2.h
diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build
index b764c5a..bff041b 100644
--- a/test cases/common/16 configure file/meson.build
+++ b/test cases/common/16 configure file/meson.build
@@ -23,18 +23,22 @@ cfile)
test('inctest', e)
# Now generate a header file with an external script.
-genprog = find_program('python3', required : false)
-if not genprog.found()
- genprog = find_program('python')
-endif
+genprog = import('python3').find_python()
scriptfile = '@0@/generator.py'.format(meson.current_source_dir())
ifile = '@0@/dummy.dat'.format(meson.current_source_dir())
ofile = '@0@/config2.h'.format(meson.current_build_dir())
+check_file = find_program('check_file.py')
+# Configure in source root with command and absolute paths
configure_file(input : 'dummy.dat',
-output : 'config2.h',
-command : [genprog, scriptfile, ifile, ofile],
-install_dir : 'share/appdir')
+ output : 'config2.h',
+ command : [genprog, scriptfile, ifile, ofile],
+ install_dir : 'share/appdir')
+run_command(check_file, join_paths(meson.current_build_dir(), 'config2.h'))
+
+found_script = find_program('generator.py')
+# More configure_file tests in here
+subdir('subdir')
test('inctest2', executable('prog2', 'prog2.c'))
diff --git a/test cases/common/16 configure file/subdir/meson.build b/test cases/common/16 configure file/subdir/meson.build
new file mode 100644
index 0000000..d802c1d
--- /dev/null
+++ b/test cases/common/16 configure file/subdir/meson.build
@@ -0,0 +1,19 @@
+# Configure in subdir with absolute paths for input and relative for output
+configure_file(input : '../dummy.dat',
+ output : 'config2-1.h',
+ command : [genprog, scriptfile, ifile, 'config2-1.h'],
+ install_dir : 'share/appdireh')
+run_command(check_file, join_paths(meson.current_build_dir(), 'config2-1.h'))
+
+# Configure in subdir with files() for input and relative for output
+configure_file(input : '../dummy.dat',
+ output : 'config2-2.h',
+ command : [genprog, scriptfile, files('../dummy.dat'), 'config2-2.h'],
+ install_dir : 'share/appdirok')
+run_command(check_file, join_paths(meson.current_build_dir(), 'config2-2.h'))
+
+# Configure in subdir with string templates for input and output
+configure_file(input : '../dummy.dat',
+ output : 'config2-3.h',
+ command : [found_script, '@INPUT@', '@OUTPUT@'])
+run_command(check_file, join_paths(meson.current_build_dir(), 'config2-3.h'))
diff --git a/test cases/common/3 static/libfile2.c b/test cases/common/3 static/libfile2.c
new file mode 100644
index 0000000..86bbb2c
--- /dev/null
+++ b/test cases/common/3 static/libfile2.c
@@ -0,0 +1,3 @@
+int libfunc2() {
+ return 4;
+}
diff --git a/test cases/common/3 static/meson.build b/test cases/common/3 static/meson.build
index 3dee93b..e539956 100644
--- a/test cases/common/3 static/meson.build
+++ b/test cases/common/3 static/meson.build
@@ -1,3 +1,4 @@
project('static library test', 'c')
-lib = static_library('mylib', 'libfile.c',
+
+lib = static_library('mylib', get_option('source'),
link_args : '-THISMUSTNOBEUSED') # Static linker needs to ignore all link args.
diff --git a/test cases/common/3 static/meson_options.txt b/test cases/common/3 static/meson_options.txt
new file mode 100644
index 0000000..7261a19
--- /dev/null
+++ b/test cases/common/3 static/meson_options.txt
@@ -0,0 +1 @@
+option('source', type : 'combo', choices : ['libfile.c', 'libfile2.c'], value : 'libfile.c')
diff --git a/test cases/common/48 test args/tester.py b/test cases/common/48 test args/tester.py
index c3c1edc..0b4010a 100755
--- a/test cases/common/48 test args/tester.py
+++ b/test cases/common/48 test args/tester.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/56 custom target/meson.build b/test cases/common/56 custom target/meson.build
index fd59fbd..2e6f69c 100644
--- a/test cases/common/56 custom target/meson.build
+++ b/test cases/common/56 custom target/meson.build
@@ -8,11 +8,13 @@ endif
# Note that this will not add a dependency to the compiler executable.
# Code will not be rebuilt if it changes.
comp = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler.py')
+# Test that files() in command: works. The compiler just discards it.
+useless = files('installed_files.txt')
mytarget = custom_target('bindat',
output : 'data.dat',
input : 'data_source.txt',
-command : [python, comp, '--input=@INPUT@', '--output=@OUTPUT@'],
+command : [python, comp, '--input=@INPUT@', '--output=@OUTPUT@', useless],
install : true,
install_dir : 'subdir'
)
diff --git a/test cases/common/56 custom target/my_compiler.py b/test cases/common/56 custom target/my_compiler.py
index 4ba2da6..f46d23a 100755
--- a/test cases/common/56 custom target/my_compiler.py
+++ b/test cases/common/56 custom target/my_compiler.py
@@ -1,16 +1,21 @@
#!/usr/bin/env python3
+import os
import sys
+assert(os.path.exists(sys.argv[3]))
+
+args = sys.argv[:-1]
+
if __name__ == '__main__':
- if len(sys.argv) != 3 or not sys.argv[1].startswith('--input') or \
- not sys.argv[2].startswith('--output'):
- print(sys.argv[0], '--input=input_file --output=output_file')
+ if len(args) != 3 or not args[1].startswith('--input') or \
+ not args[2].startswith('--output'):
+ print(args[0], '--input=input_file --output=output_file')
sys.exit(1)
- with open(sys.argv[1].split('=')[1]) as f:
+ with open(args[1].split('=')[1]) as f:
ifile = f.read()
if ifile != 'This is a text only input file.\n':
print('Malformed input')
sys.exit(1)
- with open(sys.argv[2].split('=')[1], 'w') as ofile:
+ with open(args[2].split('=')[1], 'w') as ofile:
ofile.write('This is a binary output file.\n')
diff --git a/test cases/common/58 run target/check_exists.py b/test cases/common/58 run target/check_exists.py
index 62cbe23..b6fc967 100755
--- a/test cases/common/58 run target/check_exists.py
+++ b/test cases/common/58 run target/check_exists.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
import sys
diff --git a/test cases/common/58 run target/converter.py b/test cases/common/58 run target/converter.py
index 9f47ba5..8dd31fe 100644
--- a/test cases/common/58 run target/converter.py
+++ b/test cases/common/58 run target/converter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/58 run target/fakeburner.py b/test cases/common/58 run target/fakeburner.py
index 7f505d6..da3d0ac 100755
--- a/test cases/common/58 run target/fakeburner.py
+++ b/test cases/common/58 run target/fakeburner.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
from __future__ import print_function
diff --git a/test cases/common/58 run target/meson.build b/test cases/common/58 run target/meson.build
index 8a06490..a1c5ad8 100644
--- a/test cases/common/58 run target/meson.build
+++ b/test cases/common/58 run target/meson.build
@@ -19,15 +19,17 @@ hex = custom_target('exe.hex',
],
)
+fakeburner = find_program('fakeburner.py')
+
# These emulates the Arduino flasher application. It sandwiches the filename inside
# a packed argument. Thus we need to declare it manually.
run_target('upload',
- command : ['fakeburner.py', 'x:@0@:y'.format(exe.full_path())],
+ command : [fakeburner, 'x:@0@:y'.format(exe.full_path())],
depends : exe,
)
run_target('upload2',
- command : ['fakeburner.py', 'x:@0@:y'.format(hex.full_path())],
+ command : [fakeburner, 'x:@0@:y'.format(hex.full_path())],
depends : hex,
)
diff --git a/test cases/common/60 install script/myinstall.py b/test cases/common/60 install script/myinstall.py
index 969aba5..812561e 100644
--- a/test cases/common/60 install script/myinstall.py
+++ b/test cases/common/60 install script/myinstall.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
import sys
diff --git a/test cases/common/60 install script/src/myinstall.py b/test cases/common/60 install script/src/myinstall.py
index d8a5714..3b7ce37 100644
--- a/test cases/common/60 install script/src/myinstall.py
+++ b/test cases/common/60 install script/src/myinstall.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import os
import sys
diff --git a/test cases/common/61 custom target source output/generator.py b/test cases/common/61 custom target source output/generator.py
index 42532ca..3464b0a 100755
--- a/test cases/common/61 custom target source output/generator.py
+++ b/test cases/common/61 custom target source output/generator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os
diff --git a/test cases/common/64 custom header generator/makeheader.py b/test cases/common/64 custom header generator/makeheader.py
index 0c5a228..f156834 100644
--- a/test cases/common/64 custom header generator/makeheader.py
+++ b/test cases/common/64 custom header generator/makeheader.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# NOTE: this file does not have the executable bit set. This tests that
# Meson can automatically parse shebang lines.
diff --git a/test cases/common/65 multiple generators/mygen.py b/test cases/common/65 multiple generators/mygen.py
index 020a389..99dc331 100755
--- a/test cases/common/65 multiple generators/mygen.py
+++ b/test cases/common/65 multiple generators/mygen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os
diff --git a/test cases/common/72 build always/version_gen.py b/test cases/common/72 build always/version_gen.py
index 3973e61..d7b01ca 100755
--- a/test cases/common/72 build always/version_gen.py
+++ b/test cases/common/72 build always/version_gen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os, subprocess
diff --git a/test cases/common/76 configure file in custom target/src/mycompiler.py b/test cases/common/76 configure file in custom target/src/mycompiler.py
index e1750f8..b00c862 100644
--- a/test cases/common/76 configure file in custom target/src/mycompiler.py
+++ b/test cases/common/76 configure file in custom target/src/mycompiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/77 external test program/mytest.py b/test cases/common/77 external test program/mytest.py
index 7cdaf09..9947773 100755
--- a/test cases/common/77 external test program/mytest.py
+++ b/test cases/common/77 external test program/mytest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
from __future__ import print_function
diff --git a/test cases/common/78 ctarget dependency/gen1.py b/test cases/common/78 ctarget dependency/gen1.py
index f920e53..0fa6ea1 100755
--- a/test cases/common/78 ctarget dependency/gen1.py
+++ b/test cases/common/78 ctarget dependency/gen1.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import time, sys
diff --git a/test cases/common/78 ctarget dependency/gen2.py b/test cases/common/78 ctarget dependency/gen2.py
index fc60e1e..b087b02 100755
--- a/test cases/common/78 ctarget dependency/gen2.py
+++ b/test cases/common/78 ctarget dependency/gen2.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os
from glob import glob
diff --git a/test cases/common/93 private include/stlib/compiler.py b/test cases/common/93 private include/stlib/compiler.py
index 0555c16..98dbe46 100755
--- a/test cases/common/93 private include/stlib/compiler.py
+++ b/test cases/common/93 private include/stlib/compiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys, os
diff --git a/test cases/common/94 default options/meson.build b/test cases/common/94 default options/meson.build
index a9176e0..9f45df0 100644
--- a/test cases/common/94 default options/meson.build
+++ b/test cases/common/94 default options/meson.build
@@ -1,4 +1,5 @@
project('default options', 'cpp', 'c', default_options : [
+ 'prefix=/absoluteprefix',
'buildtype=debugoptimized',
'cpp_std=c++11',
'cpp_eh=none',
diff --git a/test cases/common/95 dep fallback/gensrc.py b/test cases/common/95 dep fallback/gensrc.py
index da503e2..ff42ac3 100644
--- a/test cases/common/95 dep fallback/gensrc.py
+++ b/test cases/common/95 dep fallback/gensrc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
import shutil
diff --git a/test cases/common/95 dep fallback/subprojects/boblib/genbob.py b/test cases/common/95 dep fallback/subprojects/boblib/genbob.py
index 7da3233..34af779 100644
--- a/test cases/common/95 dep fallback/subprojects/boblib/genbob.py
+++ b/test cases/common/95 dep fallback/subprojects/boblib/genbob.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
diff --git a/test cases/common/98 gen extra/srcgen.py b/test cases/common/98 gen extra/srcgen.py
index 86fd698..8988cd9 100755
--- a/test cases/common/98 gen extra/srcgen.py
+++ b/test cases/common/98 gen extra/srcgen.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import sys
import argparse
diff --git a/test cases/failing/42 abs subdir/bob/meson.build b/test cases/failing/42 abs subdir/bob/meson.build
new file mode 100644
index 0000000..7bbf4b2
--- /dev/null
+++ b/test cases/failing/42 abs subdir/bob/meson.build
@@ -0,0 +1,2 @@
+# This file is never reached.
+x = 3
diff --git a/test cases/failing/42 abs subdir/meson.build b/test cases/failing/42 abs subdir/meson.build
new file mode 100644
index 0000000..8c23224
--- /dev/null
+++ b/test cases/failing/42 abs subdir/meson.build
@@ -0,0 +1,6 @@
+project('abs subdir', 'c')
+
+# For some reason people insist on doing this, probably
+# because Make has taught them to never rely on anything.
+subdir(join_paths(meson.source_root(), 'bob'))
+
diff --git a/test cases/failing/42 abspath to srcdir/meson.build b/test cases/failing/42 abspath to srcdir/meson.build
new file mode 100644
index 0000000..964a19b
--- /dev/null
+++ b/test cases/failing/42 abspath to srcdir/meson.build
@@ -0,0 +1,3 @@
+project('meson', 'c')
+
+include_directories(meson.current_source_dir())
diff --git a/test cases/failing/42 custom target plainname many inputs/1.txt b/test cases/failing/42 custom target plainname many inputs/1.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/test cases/failing/42 custom target plainname many inputs/1.txt
@@ -0,0 +1 @@
+1
diff --git a/test cases/failing/42 custom target plainname many inputs/2.txt b/test cases/failing/42 custom target plainname many inputs/2.txt
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/test cases/failing/42 custom target plainname many inputs/2.txt
@@ -0,0 +1 @@
+2
diff --git a/test cases/failing/42 custom target plainname many inputs/catfiles.py b/test cases/failing/42 custom target plainname many inputs/catfiles.py
new file mode 100644
index 0000000..1c53e24
--- /dev/null
+++ b/test cases/failing/42 custom target plainname many inputs/catfiles.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+
+import sys
+
+out = sys.argv[-1]
+with open(out, 'wb') as o:
+ for infile in sys.argv[1:-1]:
+ with open(infile, 'rb') as f:
+ o.write(f.read())
diff --git a/test cases/failing/42 custom target plainname many inputs/meson.build b/test cases/failing/42 custom target plainname many inputs/meson.build
new file mode 100644
index 0000000..1bcfc06
--- /dev/null
+++ b/test cases/failing/42 custom target plainname many inputs/meson.build
@@ -0,0 +1,8 @@
+project('plain name many inputs', 'c')
+
+catfiles = find_program('catfiles.py')
+
+custom_target('plainname-inputs',
+ input : ['1.txt', '2.txt'],
+ output : '@PLAINNAME@.dat',
+ command : [catfiles, '@INPUT@', '@OUTPUT@'])
diff --git a/test cases/frameworks/7 gnome/resources-data/meson.build b/test cases/frameworks/7 gnome/resources-data/meson.build
index fd8cb0a..9458c2d 100644
--- a/test cases/frameworks/7 gnome/resources-data/meson.build
+++ b/test cases/frameworks/7 gnome/resources-data/meson.build
@@ -1,5 +1,7 @@
subdir('subdir')
+python3 = import('python3').find_python()
+
fake_generator_script = '''
import os, sys
assert os.path.exists(sys.argv[1]), "File %s not found" % sys.argv[1]
@@ -11,6 +13,6 @@ print("This is a generated resource.")
res3_txt = custom_target('res3.txt',
input: 'res3.txt.in',
output: 'res3.txt',
- command: ['python3', '-c', fake_generator_script, '@INPUT@'],
+ command: [python3, '-c', fake_generator_script, '@INPUT@'],
capture: true,
)
diff --git a/test cases/frameworks/7 gnome/resources/copyfile.py b/test cases/frameworks/7 gnome/resources/copyfile.py
new file mode 100644
index 0000000..7e44c48
--- /dev/null
+++ b/test cases/frameworks/7 gnome/resources/copyfile.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import sys
+import shutil
+
+shutil.copy(sys.argv[1], sys.argv[2])
diff --git a/test cases/frameworks/7 gnome/resources/meson.build b/test cases/frameworks/7 gnome/resources/meson.build
index 2e72501..fdf6f63 100644
--- a/test cases/frameworks/7 gnome/resources/meson.build
+++ b/test cases/frameworks/7 gnome/resources/meson.build
@@ -1,8 +1,15 @@
# There are two tests here, because the 2nd one depends on a version of
-# GLib (2.48.2) that is very recent at the time of writing.
+# GLib (2.51.1) that is very recent at the time of writing.
+
+copyfile = find_program('copyfile.py')
+
+simple_gresource = configure_file(
+ input : 'simple.gresource.xml',
+ output : 'simple-gen.gresource.xml',
+ command : [copyfile, '@INPUT@', '@OUTPUT@'])
simple_resources = gnome.compile_resources('simple-resources',
- 'simple.gresource.xml',
+ simple_gresource,
install_header : true,
export : true,
source_dir : '../resources-data',
diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build
index 20b3df5..1b01cd6 100644
--- a/test cases/linuxlike/5 dependency versions/meson.build
+++ b/test cases/linuxlike/5 dependency versions/meson.build
@@ -1,4 +1,4 @@
-project('dep versions', 'c')
+project('dep versions', 'c', 'cpp')
# Find external dependency without version
zlib = dependency('zlib')
diff --git a/test cases/objc/2 nsstring/meson.build b/test cases/objc/2 nsstring/meson.build
index bc997bc..ec496a2 100644
--- a/test cases/objc/2 nsstring/meson.build
+++ b/test cases/objc/2 nsstring/meson.build
@@ -4,6 +4,9 @@ if host_machine.system() == 'darwin'
dep = dependency('appleframeworks', modules : 'foundation')
else
dep = dependency('gnustep')
+ if host_machine.system() == 'linux' and meson.get_compiler('objc').get_id() == 'clang'
+ error('MESON_SKIP_TEST: GNUstep is broken on Linux with Clang')
+ endif
endif
exe = executable('stringprog', 'stringprog.m', dependencies : dep)
test('stringtest', exe)
diff --git a/test cases/unit/2 testsetups/buggy.c b/test cases/unit/2 testsetups/buggy.c
index 5d20a24..d238830 100644
--- a/test cases/unit/2 testsetups/buggy.c
+++ b/test cases/unit/2 testsetups/buggy.c
@@ -5,10 +5,10 @@
int main(int argc, char **argv) {
char *ten = malloc(10);
- do_nasty(ten);
- free(ten);
if(getenv("TEST_ENV")) {
+ do_nasty(ten);
printf("TEST_ENV is set.\n");
}
+ free(ten);
return 0;
}
diff --git a/test cases/unit/2 testsetups/meson.build b/test cases/unit/2 testsetups/meson.build
index a65548e..488cf21 100644
--- a/test cases/unit/2 testsetups/meson.build
+++ b/test cases/unit/2 testsetups/meson.build
@@ -14,3 +14,7 @@ add_test_setup('valgrind',
buggy = executable('buggy', 'buggy.c', 'impl.c')
test('Test buggy', buggy)
+add_test_setup('empty')
+add_test_setup('onlyenv', env : env)
+add_test_setup('wrapper', exe_wrapper : [vg, '--error-exitcode=1'])
+add_test_setup('timeout', timeout_multiplier : 20)
diff --git a/test cases/unit/5 compiler detection/compiler wrapper.py b/test cases/unit/5 compiler detection/compiler wrapper.py
new file mode 100644
index 0000000..fedd343
--- /dev/null
+++ b/test cases/unit/5 compiler detection/compiler wrapper.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import sys
+import subprocess
+
+sys.exit(subprocess.call(sys.argv[1:]))
diff --git a/test cases/unit/5 compiler detection/meson.build b/test cases/unit/5 compiler detection/meson.build
new file mode 100644
index 0000000..5491c64
--- /dev/null
+++ b/test cases/unit/5 compiler detection/meson.build
@@ -0,0 +1,8 @@
+project('trivial test',
+ ['c', 'cpp', 'objc', 'objcpp'],
+ meson_version : '>=0.27.0')
+
+executable('trivialc', 'trivial.c')
+executable('trivialcpp', 'trivial.cpp')
+executable('trivialobjc', 'trivial.m')
+executable('trivialobjcpp', 'trivial.mm')
diff --git a/test cases/unit/5 compiler detection/trivial.c b/test cases/unit/5 compiler detection/trivial.c
new file mode 100644
index 0000000..24ac454
--- /dev/null
+++ b/test cases/unit/5 compiler detection/trivial.c
@@ -0,0 +1,6 @@
+#include<stdio.h>
+
+int main(int argc, char **argv) {
+ printf("Trivial test is working.\n");
+ return 0;
+}
diff --git a/test cases/unit/5 compiler detection/trivial.cc b/test cases/unit/5 compiler detection/trivial.cc
new file mode 100644
index 0000000..8aa907b
--- /dev/null
+++ b/test cases/unit/5 compiler detection/trivial.cc
@@ -0,0 +1,6 @@
+#include<iostream>
+
+int main(int argc, char **argv) {
+ std::cout << "C++ seems to be working." << std::endl;
+ return 0;
+}
diff --git a/test cases/unit/5 compiler detection/trivial.m b/test cases/unit/5 compiler detection/trivial.m
new file mode 100644
index 0000000..f2e2315
--- /dev/null
+++ b/test cases/unit/5 compiler detection/trivial.m
@@ -0,0 +1,5 @@
+#import<stdio.h>
+
+int main(int argc, char **argv) {
+ return 0;
+} \ No newline at end of file
diff --git a/test cases/unit/5 compiler detection/trivial.mm b/test cases/unit/5 compiler detection/trivial.mm
new file mode 100644
index 0000000..927e810
--- /dev/null
+++ b/test cases/unit/5 compiler detection/trivial.mm
@@ -0,0 +1,9 @@
+#import<stdio.h>
+
+class MyClass {
+};
+
+int main(int argc, char **argv) {
+ return 0;
+}
+
diff --git a/test cases/windows/9 find program/meson.build b/test cases/windows/9 find program/meson.build
index ef34586..565fb62 100644
--- a/test cases/windows/9 find program/meson.build
+++ b/test cases/windows/9 find program/meson.build
@@ -1,4 +1,12 @@
project('find program', 'c')
+# Test that we can find native windows executables
+find_program('cmd')
+find_program('cmd.exe')
+
+# Test that a script file with an extension can be found
+ext = find_program('test-script-ext.py')
+test('ext', ext)
+# Test that a script file without an extension can be found
prog = find_program('test-script')
test('script', prog)
diff --git a/test cases/windows/9 find program/test-script-ext.py b/test cases/windows/9 find program/test-script-ext.py
new file mode 100644
index 0000000..ae9adfb
--- /dev/null
+++ b/test cases/windows/9 find program/test-script-ext.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+
+print('ext/noext')