diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 46f856351..7372c4c69 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -215,13 +215,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 +235,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 +444,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 +471,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) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a22e0aba2..27e1e9aa7 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(':', '$:') @@ -1238,10 +1240,16 @@ int dummy; ''' else: 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(static_linker.get_exelist()), - output_args=' '.join(static_linker.get_output_args('$out')) - ) + 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) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 666da7d65..547889c02 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/dependencies.py b/mesonbuild/dependencies.py index 9525ffa34..e4317f1e1 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -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 @@ -994,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: @@ -1007,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/interpreter.py b/mesonbuild/interpreter.py index 224f98ede..f6065d5a0 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -285,16 +285,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 +308,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 +1421,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 +1757,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 +1766,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(): diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index e30500f2b..6eab76e46 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/modules/qt4.py b/mesonbuild/modules/qt4.py index 33c9f806e..714673948 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 b4f14759e..2a87a8016 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 bd8a3c474..bbfeaa06d 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/run_tests.py b/run_tests.py index 005717e07..f2038e4cb 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. @@ -21,10 +21,12 @@ from mesonbuild import mesonlib if __name__ == '__main__': returncode = 0 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 aed1412cc..95e52e3b1 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 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,18 @@ 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))) + self.assertEqual(commonpath([prefix, libdir]), str(PurePath(prefix))) -class LinuxlikeTests(unittest.TestCase): +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 +203,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 +211,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 +243,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 +283,336 @@ 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() + 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_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') + + +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 +651,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 +707,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 +810,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 +819,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 +871,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 +924,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 +955,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/test cases/common/105 find program path/meson.build b/test cases/common/105 find program path/meson.build index ba6030b7a..e1e6d2e4e 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/119 pathjoin/meson.build b/test cases/common/119 pathjoin/meson.build index 7f33791d7..751ca68ac 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/3 static/libfile2.c b/test cases/common/3 static/libfile2.c new file mode 100644 index 000000000..86bbb2c35 --- /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 3dee93b78..e53995649 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 000000000..7261a1992 --- /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/94 default options/meson.build b/test cases/common/94 default options/meson.build index a9176e0a9..9f45df072 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/windows/9 find program/meson.build b/test cases/windows/9 find program/meson.build index ef3458685..565fb626d 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 000000000..ae9adfb8e --- /dev/null +++ b/test cases/windows/9 find program/test-script-ext.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +print('ext/noext')