diff options
61 files changed, 455 insertions, 71 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index adc13b8..ce56a12 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -41,7 +41,6 @@ install: - ps: (new-object net.webclient).DownloadFile('https://dl.dropboxusercontent.com/u/37517477/ninja.exe', 'C:\projects\meson\ninja.exe') - cmd: if %arch%==x86 (set MESON_PYTHON_PATH=C:\python34) else (set MESON_PYTHON_PATH=C:\python34-x64) - cmd: echo Using Python at %MESON_PYTHON_PATH% - - cmd: copy %MESON_PYTHON_PATH%\python.exe %MESON_PYTHON_PATH%\python3.exe - cmd: if %compiler%==msvc2010 ( call "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" %arch% ) - cmd: if %compiler%==msvc2015 ( call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %arch% ) @@ -51,7 +50,7 @@ build_script: test_script: - cmd: echo Running tests for %arch% and %compiler% with the %backend% backend - - cmd: PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%; && python3 run_tests.py --backend=%backend% + - cmd: PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%; && python run_tests.py --backend=%backend% on_finish: - appveyor PushArtifact meson-test-run.txt -DeploymentName "Text test logs" diff --git a/man/mesontest.1 b/man/mesontest.1 new file mode 100644 index 0000000..47fdd15 --- /dev/null +++ b/man/mesontest.1 @@ -0,0 +1,57 @@ +.TH MESON "1" "December 2016" "meson 0.37.1" "User Commands" +.SH NAME +mesontest - test tool for the Meson build system +.SH DESCRIPTION + +Mesontest is a helper tool for running test suites of projects using Meson. +The default way of running tests is to invoke the default build command: + +\fBninja [\fR \fItest\fR \fB]\fR + +Mesontest provides a much richer set of tools for invoking tests. + +.SS "options:" +.TP +\fB\-\-repeat\fR +run tests as many times as specified +.TP +\fB\-\-gdb\fR +run tests under gdb +.TP +\fB\-\-list\fR +list all available tests +.TP +\fB\-\-wrapper\fR +invoke all tests via the given wrapper (e.g. valgrind) +.TP +\fB\-C\fR +Change into the given directory before running tests (must be root of build directory). +.TP +\fB\-\-suite\fR +run tests in this suite +.TP +\fB\-\-no\-suite\fR +do not run tests in this suite +.TP +\fB\-\-no\-stdsplit\fR +do not split stderr and stdout in test logs +.TP +\fB\-\-benchmark\fR +run benchmarks instead of tests +.TP +\fB\-\-logbase\fR +base of file name to use for writing test logs +.TP +\fB\-\-num-processes\fR +how many parallel processes to use to run tests +.TP +\fB\-\-verbose\fR +do not redirect stdout and stderr +.TP +\fB\-t\fR +a multiplier to use for test timeout values (usually something like 100 for Valgrind) +.TP +\fB\-\-setup\fR +use the specified test setup +.SH SEE ALSO +http://mesonbuild.com/ diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e46c2c5..6f8a50e 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -459,7 +459,8 @@ class Backend: mfobj['projects'] = self.build.dep_manifest with open(ifilename, 'w') as f: f.write(json.dumps(mfobj)) - d.data.append([ifilename, ofilename]) + # Copy file from, to, and with mode unchanged + d.data.append([ifilename, ofilename, None]) def get_regen_filelist(self): '''List of all files whose alteration means that the build diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e1a478c..628718f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -700,7 +700,7 @@ int dummy; assert(isinstance(f, mesonlib.File)) plain_f = os.path.split(f.fname)[1] dstabs = os.path.join(subdir, plain_f) - i = [f.absolute_path(srcdir, builddir), dstabs] + i = [f.absolute_path(srcdir, builddir), dstabs, de.install_mode] d.data.append(i) def generate_subdir_install(self, d): @@ -715,7 +715,7 @@ int dummy; inst_dir = sd.installable_subdir src_dir = os.path.join(self.environment.get_source_dir(), subdir) dst_dir = os.path.join(self.environment.get_prefix(), sd.install_dir) - d.install_subdirs.append([src_dir, inst_dir, dst_dir]) + d.install_subdirs.append([src_dir, inst_dir, dst_dir, sd.install_mode]) def generate_tests(self, outfile): self.serialise_tests() diff --git a/mesonbuild/build.py b/mesonbuild/build.py index ceae49b..a9f08c3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1254,6 +1254,7 @@ class CustomTarget(Target): 'depends': True, 'depend_files': True, 'depfile': True, + 'build_by_default': True, } def __init__(self, name, subdir, kwargs, absolute_paths=False): @@ -1502,9 +1503,10 @@ class ConfigurationData: # A bit poorly named, but this represents plain data files to copy # during install. class Data: - def __init__(self, sources, install_dir): + def __init__(self, sources, install_dir, install_mode=None): self.sources = sources self.install_dir = install_dir + self.install_mode = install_mode if not isinstance(self.sources, list): self.sources = [self.sources] for s in self.sources: diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index de14d4a..0a88d6b 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -610,7 +610,7 @@ class CCompiler(Compiler): return ['--coverage'] def get_coverage_link_args(self): - return ['-lgcov'] + return ['--coverage'] def get_werror_args(self): return ['-Werror'] diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 97aec7e..6ae91d4 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -119,8 +119,10 @@ class PkgConfigDependency(Dependency): if self.required: raise DependencyException('Pkg-config binary missing from cross file') else: - self.pkgbin = environment.cross_info.config['binaries']['pkgconfig'] - PkgConfigDependency.class_pkgbin = self.pkgbin + potential_pkgbin = environment.cross_info.config['binaries'].get('pkgconfig', 'non_existing_binary') + if shutil.which(potential_pkgbin): + self.pkgbin = potential_pkgbin + PkgConfigDependency.class_pkgbin = self.pkgbin # Only search for the native pkg-config the first time and # store the result in the class definition elif PkgConfigDependency.class_pkgbin is None: @@ -316,6 +318,7 @@ class WxDependency(Dependency): def __init__(self, environment, kwargs): Dependency.__init__(self, 'wx') self.is_found = False + self.modversion = 'none' if WxDependency.wx_found is None: self.check_wxconfig() if not WxDependency.wx_found: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b5cd0b6..cb5b617 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -22,7 +22,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap from . import mesonlib -from .mesonlib import Popen_safe +from .mesonlib import FileMode, Popen_safe from .dependencies import InternalDependency, Dependency from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs @@ -453,11 +453,12 @@ class DataHolder(InterpreterObject): return self.held_object.install_dir class InstallDir(InterpreterObject): - def __init__(self, source_subdir, installable_subdir, install_dir): + def __init__(self, src_subdir, inst_subdir, install_dir, install_mode): InterpreterObject.__init__(self) - self.source_subdir = source_subdir - self.installable_subdir = installable_subdir + self.source_subdir = src_subdir + self.installable_subdir = inst_subdir self.install_dir = install_dir + self.install_mode = install_mode class Man(InterpreterObject): @@ -2141,6 +2142,25 @@ requirements use the version keyword argument instead.''') self.evaluate_codeblock(codeblock) self.subdir = prev_subdir + def _get_kwarg_install_mode(self, kwargs): + if 'install_mode' not in kwargs: + return None + install_mode = [] + mode = mesonlib.stringintlistify(kwargs.get('install_mode', [])) + for m in mode: + # We skip any arguments that are set to `false` + if m is False: + m = None + install_mode.append(m) + if len(install_mode) > 3: + raise InvalidArguments('Keyword argument install_mode takes at ' + 'most 3 arguments.') + if len(install_mode) > 0 and install_mode[0] is not None and \ + not isinstance(install_mode[0], str): + raise InvalidArguments('Keyword argument install_mode requires the ' + 'permissions arg to be a string or false') + return FileMode(*install_mode) + def func_install_data(self, node, args, kwargs): kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) raw_sources = args + kwsource @@ -2153,7 +2173,10 @@ requirements use the version keyword argument instead.''') source_strings.append(s) sources += self.source_strings_to_files(source_strings) install_dir = kwargs.get('install_dir', None) - data = DataHolder(build.Data(sources, install_dir)) + if not isinstance(install_dir, (str, type(None))): + raise InvalidArguments('Keyword argument install_dir not a string.') + install_mode = self._get_kwarg_install_mode(kwargs) + data = DataHolder(build.Data(sources, install_dir, install_mode)) self.build.data.append(data.held_object) return data @@ -2166,7 +2189,8 @@ requirements use the version keyword argument instead.''') install_dir = kwargs['install_dir'] if not isinstance(install_dir, str): raise InvalidArguments('Keyword argument install_dir not a string.') - idir = InstallDir(self.subdir, args[0], install_dir) + install_mode = self._get_kwarg_install_mode(kwargs) + idir = InstallDir(self.subdir, args[0], install_dir, install_mode) self.build.install_dirs.append(idir) return idir diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index e4dd58b..837a4f8 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -1,4 +1,4 @@ -# 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. @@ -86,6 +86,7 @@ class InterpreterBase: self.builtin = {} self.subdir = subdir self.variables = {} + self.argument_depth = 0 def load_root_meson_file(self): mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) @@ -511,6 +512,7 @@ class InterpreterBase: assert(isinstance(args, mparser.ArgumentNode)) if args.incorrect_order(): raise InvalidArguments('All keyword arguments must be after positional arguments.') + self.argument_depth += 1 reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] reduced_kw = {} for key in args.kwargs.keys(): @@ -520,6 +522,7 @@ class InterpreterBase: reduced_kw[key] = self.evaluate_statement(a) if not isinstance(reduced_pos, list): reduced_pos = [reduced_pos] + self.argument_depth -= 1 return reduced_pos, reduced_kw def flatten(self, args): @@ -540,6 +543,9 @@ class InterpreterBase: def assignment(self, node): assert(isinstance(node, mparser.AssignmentNode)) + if self.argument_depth != 0: + raise InvalidArguments('''Tried to assign values inside an argument list. +To specify a keyword argument, use : instead of =.''') var_name = node.var_name if not isinstance(var_name, str): raise InvalidArguments('Tried to assign value to a non-variable.') @@ -551,7 +557,7 @@ class InterpreterBase: if isinstance(value, MutableInterpreterObject): value = copy.deepcopy(value) self.set_variable(var_name, value) - return value + return None def set_variable(self, varname, variable): if variable is None: diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index c921409..2ab5f92 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -22,6 +22,8 @@ parser = argparse.ArgumentParser() parser.add_argument('-D', action='append', default=[], dest='sets', help='Set an option to the given value.') parser.add_argument('directory', nargs='*') +parser.add_argument('--clearcache', action='store_true', default=False, + help='Clear cached state (e.g. found dependencies)') class ConfException(mesonlib.MesonException): def __init__(self, *args, **kwargs): @@ -42,13 +44,16 @@ class Conf: raise ConfException('Version mismatch (%s vs %s)' % (coredata.version, self.coredata.version)) + def clear_cache(self): + self.coredata.deps = {} + def save(self): # Only called if something has changed so overwrite unconditionally. with open(self.coredata_file, 'wb') as f: pickle.dump(self.coredata, f) # We don't write the build file because any changes to it - # are erased when Meson is executed the nex time, i.e. the next - # time Ninja is run. + # are erased when Meson is executed the next time, i.e. whne + # Ninja is run. def print_aligned(self, arr): if len(arr) == 0: @@ -223,11 +228,17 @@ def run(args): builddir = options.directory[0] try: c = Conf(builddir) + save = False if len(options.sets) > 0: c.set_options(options.sets) - c.save() + save = True + elif options.clearcache: + c.clear_cache() + save = True else: c.print_conf() + if save: + c.save() except ConfException as e: print('Meson configurator encountered an error:\n') print(e) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 2587d6f..2ad43c8 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -14,6 +14,7 @@ """A library of random helper functionality.""" +import stat import platform, subprocess, operator, os, shutil, re from glob import glob @@ -24,6 +25,97 @@ class MesonException(Exception): class EnvironmentException(MesonException): '''Exceptions thrown while processing and creating the build environment''' +class FileMode: + # The first triad is for owner permissions, the second for group permissions, + # and the third for others (everyone else). + # For the 1st character: + # 'r' means can read + # '-' means not allowed + # For the 2nd character: + # 'w' means can write + # '-' means not allowed + # For the 3rd character: + # 'x' means can execute + # 's' means can execute and setuid/setgid is set (owner/group triads only) + # 'S' means cannot execute and setuid/setgid is set (owner/group triads only) + # 't' means can execute and sticky bit is set ("others" triads only) + # 'T' means cannot execute and sticky bit is set ("others" triads only) + # '-' means none of these are allowed + # + # The meanings of 'rwx' perms is not obvious for directories; see: + # https://www.hackinglinuxexposed.com/articles/20030424.html + # + # For information on this notation such as setuid/setgid/sticky bits, see: + # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation + symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms + '[r-][w-][xsS-]' # Group perms + '[r-][w-][xtT-]') # Others perms + + def __init__(self, perms=None, owner=None, group=None): + self.perms_s = perms + self.perms = self.perms_s_to_bits(perms) + self.owner = owner + self.group = group + + def __repr__(self): + ret = '<FileMode: {!r} owner={} group={}' + return ret.format(self.perms_s, self.owner, self.group) + + @classmethod + def perms_s_to_bits(cls, perms_s): + ''' + Does the opposite of stat.filemode(), converts strings of the form + 'rwxr-xr-x' to st_mode enums which can be passed to os.chmod() + ''' + if perms_s is None: + # No perms specified, we will not touch the permissions + return -1 + eg = 'rwxr-xr-x' + if not isinstance(perms_s, str): + msg = 'Install perms must be a string. For example, {!r}' + raise MesonException(msg.format(eg)) + if len(perms_s) != 9 or not cls.symbolic_perms_regex.match(perms_s): + msg = 'File perms {!r} must be exactly 9 chars. For example, {!r}' + raise MesonException(msg.format(perms_s, eg)) + perms = 0 + # Owner perms + if perms_s[0] == 'r': + perms |= stat.S_IRUSR + if perms_s[1] == 'w': + perms |= stat.S_IWUSR + if perms_s[2] == 'x': + perms |= stat.S_IXUSR + elif perms_s[2] == 'S': + perms |= stat.S_ISUID + elif perms_s[2] == 's': + perms |= stat.S_IXUSR + perms |= stat.S_ISUID + # Group perms + if perms_s[3] == 'r': + perms |= stat.S_IRGRP + if perms_s[4] == 'w': + perms |= stat.S_IWGRP + if perms_s[5] == 'x': + perms |= stat.S_IXGRP + elif perms_s[5] == 'S': + perms |= stat.S_ISGID + elif perms_s[5] == 's': + perms |= stat.S_IXGRP + perms |= stat.S_ISGID + # Others perms + if perms_s[6] == 'r': + perms |= stat.S_IROTH + if perms_s[7] == 'w': + perms |= stat.S_IWOTH + if perms_s[8] == 'x': + perms |= stat.S_IXOTH + elif perms_s[8] == 'T': + perms |= stat.S_ISVTX + elif perms_s[8] == 't': + perms |= stat.S_IXOTH + perms |= stat.S_ISVTX + return perms + class File: def __init__(self, is_built, subdir, fname): self.is_built = is_built @@ -360,11 +452,21 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) +def stringintlistify(item): + if isinstance(item, (str, int)): + item = [item] + if not isinstance(item, list): + raise MesonException('Item must be a list, a string, or an int') + for i in item: + if not isinstance(i, (str, int, type(None))): + raise MesonException('List item must be a string or an int') + return item + def stringlistify(item): if isinstance(item, str): item = [item] if not isinstance(item, list): - raise MesonException('Item is not an array') + raise MesonException('Item is not a list') for i in item: if not isinstance(i, str): raise MesonException('List item not a string.') diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 5a381c1..e30500f 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -62,7 +62,7 @@ def determine_installed_path(target, installdata): def list_installed(installdata): res = {} if installdata is not None: - for path, installpath in installdata.data: + for path, installpath, unused_prefix in installdata.data: res[path] = os.path.join(installdata.prefix, installpath) print(json.dumps(res)) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 5871f65..6e1e398 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -1,4 +1,4 @@ -# Copyright 2014-2016 The Meson development team +# Copyright 2014-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. diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 676a1e5..a74573e 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -16,10 +16,34 @@ import sys, pickle, os, shutil, subprocess, gzip, platform from glob import glob from . import depfixer from . import destdir_join -from ..mesonlib import Popen_safe +from ..mesonlib import is_windows, Popen_safe install_log_file = None +def set_mode(path, mode): + if mode is None: + # Keep mode unchanged + return + if (mode.perms_s or mode.owner or mode.group) is None: + # Nothing to set + return + # No chown() on Windows, and must set one of owner/group + if not is_windows() and (mode.owner or mode.group) is not None: + try: + shutil.chown(path, mode.owner, mode.group) + except PermissionError as e: + msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...' + print(msg.format(path, mode.owner, mode.group, e.strerror)) + # Must set permissions *after* setting owner/group otherwise the + # setuid/setgid bits will get wiped by chmod + # NOTE: On Windows you can set read/write perms; the rest are ignored + if mode.perms_s is not None: + try: + os.chmod(path, mode.perms) + except PermissionError as e: + msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...' + print(msg.format(path, mode.perms_s, e.strerror)) + def append_to_log(line): install_log_file.write(line) if not line.endswith('\n'): @@ -96,7 +120,7 @@ def do_install(datafilename): run_install_script(d) def install_subdirs(data): - for (src_dir, inst_dir, dst_dir) in data.install_subdirs: + for (src_dir, inst_dir, dst_dir, mode) in data.install_subdirs: if src_dir.endswith('/') or src_dir.endswith('\\'): src_dir = src_dir[:-1] src_prefix = os.path.join(src_dir, inst_dir) @@ -105,15 +129,19 @@ def install_subdirs(data): if not os.path.exists(dst_dir): os.makedirs(dst_dir) do_copydir(src_prefix, src_dir, dst_dir) + dst_prefix = os.path.join(dst_dir, inst_dir) + set_mode(dst_prefix, mode) def install_data(d): for i in d.data: fullfilename = i[0] outfilename = get_destdir_path(d, i[1]) + mode = i[2] outdir = os.path.split(outfilename)[0] os.makedirs(outdir, exist_ok=True) print('Installing %s to %s.' % (fullfilename, outdir)) do_copyfile(fullfilename, outfilename) + set_mode(outfilename, mode) def install_man(d): for m in d.man: diff --git a/mesontest.py b/mesontest.py index bce4100..1a0c9b1 100755 --- a/mesontest.py +++ b/mesontest.py @@ -262,7 +262,8 @@ class TestHarness: if timed_out: res = 'TIMEOUT' self.timeout_count += 1 - if p.returncode == GNU_SKIP_RETURNCODE: + self.fail_count += 1 + elif p.returncode == GNU_SKIP_RETURNCODE: res = 'SKIP' self.skip_count += 1 elif test.should_fail == bool(p.returncode): @@ -415,7 +416,7 @@ TIMEOUT: %4d jsonlogfile = open(jsonlogfilename, 'w') logfile = open(logfilename, 'w') - logfile.write('Log of Meson test suite run on %s.\n\n' + logfile.write('Log of Meson test suite run on %s\n\n' % datetime.datetime.now().isoformat()) return logfile, logfilename, jsonlogfile, jsonlogfilename @@ -483,7 +484,7 @@ TIMEOUT: %4d self.print_collected_logs() if logfilename: - print('Full log written to %s.' % logfilename) + print('Full log written to %s' % logfilename) finally: if jsonlogfile: jsonlogfile.close() diff --git a/run_project_tests.py b/run_project_tests.py index 4715dbb..e04fed1 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -105,6 +105,8 @@ def setup_commands(backend): global backend_flags, compile_commands, test_commands, install_commands, clean_commands msbuild_exe = shutil.which('msbuild') if (backend and backend.startswith('vs')) or (backend is None and msbuild_exe is not None): + if backend is None: + backend = 'vs2010' backend_flags = ['--backend=' + backend] compile_commands = ['msbuild'] test_commands = ['msbuild', 'RUN_TESTS.vcxproj'] diff --git a/run_unittests.py b/run_unittests.py index 6aa5b2b..5eba222 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -13,12 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import stat import unittest, os, sys, shutil, time import subprocess import re, json import tempfile from glob import glob import mesonbuild.environment +import mesonbuild.mesonlib from mesonbuild.environment import detect_ninja, Environment from mesonbuild.dependencies import PkgConfigDependency @@ -58,6 +60,42 @@ class InternalTests(unittest.TestCase): self.assertEqual(searchfunc('foobar 2016.10.128'), 'unknown version') self.assertEqual(searchfunc('2016.10.128'), 'unknown version') + def test_mode_symbolic_to_bits(self): + modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits + self.assertEqual(modefunc('---------'), 0) + self.assertEqual(modefunc('r--------'), stat.S_IRUSR) + self.assertEqual(modefunc('---r-----'), stat.S_IRGRP) + self.assertEqual(modefunc('------r--'), stat.S_IROTH) + self.assertEqual(modefunc('-w-------'), stat.S_IWUSR) + self.assertEqual(modefunc('----w----'), stat.S_IWGRP) + self.assertEqual(modefunc('-------w-'), stat.S_IWOTH) + self.assertEqual(modefunc('--x------'), stat.S_IXUSR) + self.assertEqual(modefunc('-----x---'), stat.S_IXGRP) + self.assertEqual(modefunc('--------x'), stat.S_IXOTH) + self.assertEqual(modefunc('--S------'), stat.S_ISUID) + self.assertEqual(modefunc('-----S---'), stat.S_ISGID) + self.assertEqual(modefunc('--------T'), stat.S_ISVTX) + self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR) + self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP) + self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH) + self.assertEqual(modefunc('rwx------'), stat.S_IRWXU) + self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG) + self.assertEqual(modefunc('------rwx'), stat.S_IRWXO) + # We could keep listing combinations exhaustively but that seems + # tedious and pointless. Just test a few more. + self.assertEqual(modefunc('rwxr-xr-x'), + stat.S_IRWXU | + stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + self.assertEqual(modefunc('rw-r--r--'), + stat.S_IRUSR | stat.S_IWUSR | + stat.S_IRGRP | + stat.S_IROTH) + self.assertEqual(modefunc('rwsr-x---'), + stat.S_IRWXU | stat.S_ISUID | + stat.S_IRGRP | stat.S_IXGRP) + + class LinuxlikeTests(unittest.TestCase): def setUp(self): super().setUp() @@ -552,6 +590,57 @@ class LinuxlikeTests(unittest.TestCase): 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. + Can't be an ordinary test because our installed_files.txt is very basic. + ''' + # Test file modes + testdir = os.path.join(self.common_test_dir, '12 data') + self.init(testdir) + self.install() + + f = os.path.join(self.installdir, 'etc', 'etcfile.dat') + found_mode = stat.filemode(os.stat(f).st_mode) + want_mode = 'rw------T' + self.assertEqual(want_mode, found_mode[1:]) + + f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh') + statf = os.stat(f) + found_mode = stat.filemode(statf.st_mode) + want_mode = 'rwxr-sr-x' + self.assertEqual(want_mode, found_mode[1:]) + if os.getuid() == 0: + # The chown failed nonfatally if we're not root + self.assertEqual(0, statf.st_uid) + self.assertEqual(0, statf.st_gid) + + f = os.path.join(self.installdir, 'usr', 'share', 'progname', + 'fileobject_datafile.dat') + statf = os.stat(f) + found_mode = stat.filemode(statf.st_mode) + want_mode = 'rw-rw-r--' + self.assertEqual(want_mode, found_mode[1:]) + self.assertEqual(os.getuid(), statf.st_uid) + if os.getuid() == 0: + # The chown failed nonfatally if we're not root + self.assertEqual(0, statf.st_gid) + + self.wipe() + # Test directory modes + testdir = os.path.join(self.common_test_dir, '66 install subdir') + self.init(testdir) + self.install() + + f = os.path.join(self.installdir, 'usr', 'share', 'sub1') + statf = os.stat(f) + found_mode = stat.filemode(statf.st_mode) + want_mode = 'rwxr-x--t' + self.assertEqual(want_mode, found_mode[1:]) + if os.getuid() == 0: + # The chown failed nonfatally if we're not root + self.assertEqual(0, statf.st_uid) + class RewriterTests(unittest.TestCase): @@ -74,6 +74,7 @@ setup(name='meson', data_files=[('share/man/man1', ['man/meson.1', 'man/mesonconf.1', 'man/mesonintrospect.1', + 'main/mesontest.1', 'man/wraptool.1'])], classifiers=['Development Status :: 5 - Production/Stable', 'Environment :: Console', diff --git a/test cases/common/103 manygen/subdir/manygen.py b/test cases/common/103 manygen/subdir/manygen.py index 8e74bcd..7ffd435 100755 --- a/test cases/common/103 manygen/subdir/manygen.py +++ b/test cases/common/103 manygen/subdir/manygen.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python + +from __future__ import print_function # Generates a static library, object file, source # file and a header file. diff --git a/test cases/common/103 manygen/subdir/meson.build b/test cases/common/103 manygen/subdir/meson.build index 3036899..73b4ff7 100644 --- a/test cases/common/103 manygen/subdir/meson.build +++ b/test cases/common/103 manygen/subdir/meson.build @@ -1,4 +1,5 @@ -gen = find_program('manygen.py') +gen = files('manygen.py') +py3_bin = import('python3').find_python() buildtype = get_option('buildtype') buildtype_args = '-Dfooxxx' # a useless compiler argument @@ -20,5 +21,5 @@ endif generated = custom_target('manygen', output : outfiles, input : ['funcinfo.def'], - command : [gen, '@INPUT@', '@OUTDIR@', buildtype_args], + command : [py3_bin, gen[0], '@INPUT@', '@OUTDIR@', buildtype_args], ) diff --git a/test cases/common/105 find program path/meson.build b/test cases/common/105 find program path/meson.build index 2ece2bc..ba6030b 100644 --- a/test cases/common/105 find program path/meson.build +++ b/test cases/common/105 find program path/meson.build @@ -2,8 +2,9 @@ project('find program', 'c') prog = find_program('program.py') -# Python 3 is guaranteed to be available because Meson -# is implemented in it. -python = find_program('python3') +python = find_program('python3', required : false) +if not python.found() + python = find_program('python') +endif run_command(python, prog.path()) diff --git a/test cases/common/105 find program path/program.py b/test cases/common/105 find program path/program.py index 2ebc564..b910718 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 python3 +#!/usr/bin/env python print("Found") diff --git a/test cases/common/107 postconf/postconf.py b/test cases/common/107 postconf/postconf.py index 950c706..9a23cfa 100644 --- a/test cases/common/107 postconf/postconf.py +++ b/test cases/common/107 postconf/postconf.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python 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 cef7f79..3ed0450 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 python3 +#!/usr/bin/env python import sys, os diff --git a/test cases/common/113 generatorcustom/catter.py b/test cases/common/113 generatorcustom/catter.py index 198fa98..a79b739 100755 --- a/test cases/common/113 generatorcustom/catter.py +++ b/test cases/common/113 generatorcustom/catter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/113 generatorcustom/gen.py b/test cases/common/113 generatorcustom/gen.py index c1e34ed..f9efb47 100755 --- a/test cases/common/113 generatorcustom/gen.py +++ b/test cases/common/113 generatorcustom/gen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/117 custom target capture/meson.build b/test cases/common/117 custom target capture/meson.build index fa59d51..d3214e8 100644 --- a/test cases/common/117 custom target capture/meson.build +++ b/test cases/common/117 custom target capture/meson.build @@ -1,6 +1,9 @@ project('custom target', 'c') -python = find_program('python3') +python3 = find_program('python3', required : false) +if not python3.found() + python3 = find_program('python') +endif # Note that this will not add a dependency to the compiler executable. # Code will not be rebuilt if it changes. @@ -10,7 +13,7 @@ mytarget = custom_target('bindat', output : 'data.dat', input : 'data_source.txt', capture : true, - command : [python, comp, '@INPUT@'], + command : [python3, comp, '@INPUT@'], install : true, install_dir : 'subdir' ) diff --git a/test cases/common/118 allgenerate/converter.py b/test cases/common/118 allgenerate/converter.py index f8e2ca0..cc2c574 100755 --- a/test cases/common/118 allgenerate/converter.py +++ b/test cases/common/118 allgenerate/converter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/12 data/installed_files.txt b/test cases/common/12 data/installed_files.txt index 8651e3a..af1a735 100644 --- a/test cases/common/12 data/installed_files.txt +++ b/test cases/common/12 data/installed_files.txt @@ -3,3 +3,4 @@ usr/share/progname/fileobject_datafile.dat usr/share/progname/vanishing.dat usr/share/progname/vanishing2.dat etc/etcfile.dat +usr/bin/runscript.sh diff --git a/test cases/common/12 data/meson.build b/test cases/common/12 data/meson.build index 7494abc..d3407d1 100644 --- a/test cases/common/12 data/meson.build +++ b/test cases/common/12 data/meson.build @@ -1,7 +1,14 @@ project('data install test', 'c') install_data(sources : 'datafile.dat', install_dir : 'share/progname') -install_data(sources : 'etcfile.dat', install_dir : '/etc') -install_data(files('fileobject_datafile.dat'), install_dir : 'share/progname') +# Some file in /etc that is only read-write by root; add a sticky bit for testing +install_data(sources : 'etcfile.dat', install_dir : '/etc', install_mode : 'rw------T') +# Some script that needs to be executable by the group +install_data('runscript.sh', + install_dir : get_option('bindir'), + install_mode : ['rwxr-sr-x', 'root', 0]) +install_data(files('fileobject_datafile.dat'), + install_dir : 'share/progname', + install_mode : [false, false, 0]) subdir('vanishing') diff --git a/test cases/common/12 data/runscript.sh b/test cases/common/12 data/runscript.sh new file mode 100644 index 0000000..8bc5ca6 --- /dev/null +++ b/test cases/common/12 data/runscript.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Runscript" 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 245f370..4d80124 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 python3 +#!/usr/bin/env python 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 99b7cdd..5bccece 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 python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/16 configure file/generator.py b/test cases/common/16 configure file/generator.py index 31a6944..2c7f2f8 100755 --- a/test cases/common/16 configure file/generator.py +++ b/test cases/common/16 configure file/generator.py @@ -1,5 +1,9 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python +# On some platforms "python" points to Python 2 +# on others to Python 3. Work with both. + +from __future__ import print_function import sys, os if len(sys.argv) != 3: diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 0c5b981..b764c5a 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -23,7 +23,10 @@ cfile) test('inctest', e) # Now generate a header file with an external script. -genprog = find_program('python3') +genprog = find_program('python3', required : false) +if not genprog.found() + genprog = find_program('python') +endif 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()) diff --git a/test cases/common/48 test args/tester.py b/test cases/common/48 test args/tester.py index 0b4010a..c3c1edc 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 python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/56 custom target/depfile/dep.py b/test cases/common/56 custom target/depfile/dep.py index 585e192..aff325b 100755 --- a/test cases/common/56 custom target/depfile/dep.py +++ b/test cases/common/56 custom target/depfile/dep.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys, os from glob import glob diff --git a/test cases/common/56 custom target/meson.build b/test cases/common/56 custom target/meson.build index feaa762..fd59fbd 100644 --- a/test cases/common/56 custom target/meson.build +++ b/test cases/common/56 custom target/meson.build @@ -1,6 +1,9 @@ project('custom target', 'c') -python = find_program('python3') +python = find_program('python3', required : false) +if not python.found() + python = find_program('python') +endif # Note that this will not add a dependency to the compiler executable. # Code will not be rebuilt if it changes. diff --git a/test cases/common/57 custom target chain/meson.build b/test cases/common/57 custom target chain/meson.build index 1af0425..138f795 100644 --- a/test cases/common/57 custom target chain/meson.build +++ b/test cases/common/57 custom target chain/meson.build @@ -1,7 +1,12 @@ project('custom target', 'c') -python = find_program('python3') +python = find_program('python3', required : false) +if not python.found() + python = find_program('python') +endif +# files() is the correct way to do this, but some people +# do this so test that it works. comp = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler.py') comp2 = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler2.py') infile = files('data_source.txt')[0] diff --git a/test cases/common/57 custom target chain/my_compiler.py b/test cases/common/57 custom target chain/my_compiler.py index d99029b..9cf4425 100755 --- a/test cases/common/57 custom target chain/my_compiler.py +++ b/test cases/common/57 custom target chain/my_compiler.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/57 custom target chain/my_compiler2.py b/test cases/common/57 custom target chain/my_compiler2.py index 22ec789..0191f3f 100755 --- a/test cases/common/57 custom target chain/my_compiler2.py +++ b/test cases/common/57 custom target chain/my_compiler2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/57 custom target chain/usetarget/subcomp.py b/test cases/common/57 custom target chain/usetarget/subcomp.py index 52dc0bb..b5f6eb0 100755 --- a/test cases/common/57 custom target chain/usetarget/subcomp.py +++ b/test cases/common/57 custom target chain/usetarget/subcomp.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/58 run target/converter.py b/test cases/common/58 run target/converter.py index 8dd31fe..9f47ba5 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 python3 +#!/usr/bin/env python import sys diff --git a/test cases/common/58 run target/fakeburner.py b/test cases/common/58 run target/fakeburner.py index 5728002..7f505d6 100755 --- a/test cases/common/58 run target/fakeburner.py +++ b/test cases/common/58 run target/fakeburner.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python + +from __future__ import print_function import sys diff --git a/test cases/common/58 run target/meson.build b/test cases/common/58 run target/meson.build index 0540b80..8a06490 100644 --- a/test cases/common/58 run target/meson.build +++ b/test cases/common/58 run target/meson.build @@ -31,7 +31,11 @@ run_target('upload2', depends : hex, ) -python3 = find_program('python3') +python3 = find_program('python3', required : false) +if not python3.found() + python3 = find_program('python') +endif + run_target('py3hi', command : [python3, '-c', 'print("I am Python3.")']) diff --git a/test cases/common/59 object generator/meson.build b/test cases/common/59 object generator/meson.build index 0bdefb8..e20da6f 100644 --- a/test cases/common/59 object generator/meson.build +++ b/test cases/common/59 object generator/meson.build @@ -1,6 +1,9 @@ project('object generator', 'c') -python = find_program('python3') +python = find_program('python3', required : false) +if not python.found() + python = find_program('python') +endif # Note that this will not add a dependency to the compiler executable. # Code will not be rebuilt if it changes. 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 3464b0a..42532ca 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 python3 +#!/usr/bin/env python 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 f156834..0c5a228 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 python3 +#!/usr/bin/env python # 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 99dc331..020a389 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 python3 +#!/usr/bin/env python import sys, os diff --git a/test cases/common/66 install subdir/meson.build b/test cases/common/66 install subdir/meson.build index 669cf09..fed2f89 100644 --- a/test cases/common/66 install subdir/meson.build +++ b/test cases/common/66 install subdir/meson.build @@ -1,5 +1,7 @@ project('install a whole subdir', 'c') subdir('subdir') -install_subdir('sub1', install_dir : 'share') +# A subdir with write perms only for the owner +# and read-list perms for owner and group +install_subdir('sub1', install_dir : 'share', install_mode : ['rwxr-x--t', 'root']) install_subdir('sub/sub1', install_dir : 'share') diff --git a/test cases/common/66 install subdir/subdir/meson.build b/test cases/common/66 install subdir/subdir/meson.build index 08b417d..37d2da4 100644 --- a/test cases/common/66 install subdir/subdir/meson.build +++ b/test cases/common/66 install subdir/subdir/meson.build @@ -1 +1,3 @@ -install_subdir('sub1', install_dir : 'share') +install_subdir('sub1', install_dir : 'share', + # This mode will be overriden by the mode set in the outer install_subdir + install_mode : 'rwxr-x---') diff --git a/test cases/common/72 build always/version_gen.py b/test cases/common/72 build always/version_gen.py index d7b01ca..3973e61 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 python3 +#!/usr/bin/env python 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 b00c862..e1750f8 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 python3 +#!/usr/bin/env python 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 03d88b8..7cdaf09 100755 --- a/test cases/common/77 external test program/mytest.py +++ b/test cases/common/77 external test program/mytest.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python + +from __future__ import print_function import sys diff --git a/test cases/common/78 ctarget dependency/gen1.py b/test cases/common/78 ctarget dependency/gen1.py index 0fa6ea1..f920e53 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 python3 +#!/usr/bin/env python import time, sys diff --git a/test cases/common/78 ctarget dependency/gen2.py b/test cases/common/78 ctarget dependency/gen2.py index b087b02..fc60e1e 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 python3 +#!/usr/bin/env python 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 98dbe46..0555c16 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 python3 +#!/usr/bin/env python import sys, os diff --git a/test cases/common/98 gen extra/srcgen.py b/test cases/common/98 gen extra/srcgen.py index 8988cd9..86fd698 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 python3 +#!/usr/bin/env python import sys import argparse diff --git a/test cases/failing/41 kwarg assign/dummy.c b/test cases/failing/41 kwarg assign/dummy.c new file mode 100644 index 0000000..16fcdd9 --- /dev/null +++ b/test cases/failing/41 kwarg assign/dummy.c @@ -0,0 +1,3 @@ +const char* dummy() { + return "I do nothing."; +} diff --git a/test cases/failing/41 kwarg assign/meson.build b/test cases/failing/41 kwarg assign/meson.build new file mode 100644 index 0000000..c86786f --- /dev/null +++ b/test cases/failing/41 kwarg assign/meson.build @@ -0,0 +1,4 @@ +project('assign in kwarg', 'c') + +executable('prog', 'dummy.c', args = 'prog.c') + diff --git a/test cases/failing/41 kwarg assign/prog.c b/test cases/failing/41 kwarg assign/prog.c new file mode 100644 index 0000000..11b7fad --- /dev/null +++ b/test cases/failing/41 kwarg assign/prog.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 0; +} |