--- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -32,7 +32,7 @@ from .. import compilers from ..compilers import CompilerArgs, CCompiler from ..linkers import ArLinker from ..mesonlib import File, MesonException, OrderedSet -from ..mesonlib import get_compiler_for_source, has_path_sep +from ..mesonlib import get_compiler_for_source, has_path_sep, commonpath from .backends import CleanTrees from ..build import InvalidArguments @@ -1011,8 +1011,8 @@ int dummy; # Check if the vala file is in a subdir of --basedir abs_srcbasedir = os.path.join(self.environment.get_source_dir(), target.get_subdir()) abs_vala_file = os.path.join(self.environment.get_build_dir(), vala_file) - if PurePath(os.path.commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir): - vala_c_subdir = PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir) + if PurePath(commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir): + vala_c_subdir = str(PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir)) vala_c_file = os.path.join(str(vala_c_subdir), vala_c_file) else: path_to_target = os.path.join(self.build_to_src, target.get_subdir()) --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -18,7 +18,7 @@ import sys from pathlib import PurePath from collections import OrderedDict from .mesonlib import MesonException -from .mesonlib import default_libdir, default_libexecdir, default_prefix +from .mesonlib import default_libdir, default_libexecdir, default_prefix, commonpath from .wrap import WrapMode import ast import argparse @@ -302,7 +302,7 @@ class CoreData: # commonpath will always return a path in the native format, so we # must use pathlib.PurePath to do the same conversion before # comparing. - if os.path.commonpath([value, prefix]) != str(PurePath(prefix)): + if commonpath([value, prefix]) != str(PurePath(prefix)): m = 'The value of the {!r} option is {!r} which must be a ' \ 'subdir of the prefix {!r}.\nNote that if you pass a ' \ 'relative path, it is assumed to be a subdir of prefix.' --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -574,7 +574,9 @@ The result of this is undefined and will if cur.operation == 'add': if isinstance(l, dict) and isinstance(r, dict): - return {**l, **r} + ret = l.copy() + ret.update(r) + return ret try: return l + r except Exception as e: @@ -661,7 +662,8 @@ The result of this is undefined and will elif isinstance(old_variable, dict): if not isinstance(addition, dict): raise InvalidArguments('The += operator requires a dict on the right hand side if the variable on the left is a dict') - new_value = {**old_variable, **addition} + new_value = old_variable.copy() + new_value.update(addition) # Add other data types here. else: raise InvalidArguments('The += operator currently only works with arrays, dicts, strings or ints ') --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -19,7 +19,8 @@ import sys import stat import time import platform, subprocess, operator, os, shutil, re -import collections +import collections +import collections.abc from mesonbuild import mlog have_fcntl = False @@ -1062,6 +1062,30 @@ def substring_is_in_list(substr, strlist return True return False +def commonpath(paths): + ''' + For use on Python 3.4 where os.path.commonpath is not available. + ''' + if sys.version_info >= (3, 5): + return os.path.commonpath(paths) + + import pathlib + if not paths: + raise ValueError('commonpath() arg is an empty sequence') + common = pathlib.PurePath(paths[0]) + for path in paths[1:]: + new = [] + path = pathlib.PurePath(path) + for c, p in zip(common.parts, path.parts): + if c != p: + break + new.append(c) + if not new: + raise ValueError("Can't mix absolute and relative paths") from None + new = os.path.join(*new) + common = pathlib.PurePath(new) + return str(common) + class OrderedSet(collections.abc.MutableSet): """A set that preserves the order in which items are added, by first insertion. --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -269,8 +269,8 @@ def set_meson_command(mainfile): mlog.log('meson_command is {!r}'.format(mesonlib.meson_command)) def run(original_args, mainfile): - if sys.version_info < (3, 5): - print('Meson works correctly only with python 3.5+.') + if sys.version_info < (3, 4): + print('Meson works correctly only with python 3.4+.') print('You have python %s.' % sys.version) print('Please update your environment') return 1 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -353,8 +353,8 @@ class Installer: if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ: print('Installation failed due to insufficient permissions.') print('Attempting to use polkit to gain elevated privileges...') - os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:], - '-C', os.getcwd()) + os.execvp('pkexec', ['pkexec', sys.executable, main_file] + sys.argv[1:] + + ['-C', os.getcwd()]) else: raise --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -512,7 +512,7 @@ class PythonModule(ExtensionModule): # Sanity check, we expect to have something that at least quacks in tune try: info = json.loads(run_command(python, INTROSPECT_COMMAND)) - except json.JSONDecodeError: + except ValueError: info = None if isinstance(info, dict) and 'version' in info and self._check_version(name_or_path, info['version']): --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -115,7 +115,12 @@ def returncode_to_status(retcode): if retcode < 0: signum = -retcode try: - signame = signal.Signals(signum).name + if sys.version_info >= (3, 5): + signame = signal.Signals(signum).name + else: + try: signame = [next(n for n, i in signal.__dict__.items() + if i == signum)] + except StopIteration: raise ValueError except ValueError: signame = 'SIGinvalid' return '(killed by signal %d %s)' % (signum, signame) @@ -125,7 +130,12 @@ def returncode_to_status(retcode): signum = retcode - 128 try: - signame = signal.Signals(signum).name + if sys.version_info >= (3, 5): + signame = signal.Signals(signum).name + else: + try: signame = [next(n for n, i in signal.__dict__.items() + if i == signum)] + except StopIteration: raise ValueError except ValueError: signame = 'SIGinvalid' return '(exit status %d or signal %d %s)' % (retcode, signum, signame) --- a/mesonbuild/scripts/dist.py +++ b/mesonbuild/scripts/dist.py @@ -87,8 +87,8 @@ def run_dist_scripts(dist_root, dist_scr silent=True) if not ep.found(): sys.exit('Script %s could not be found in dist directory.' % d) - pc = subprocess.run(ep.command, env=env) - if pc.returncode != 0: + pc = subprocess.call(ep.command, env=env) + if pc != 0: sys.exit('Dist script errored out.') def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts): --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -17,7 +17,7 @@ import subprocess import shlex import shutil import argparse -from ..mesonlib import MesonException, Popen_safe, is_windows +from ..mesonlib import MesonException, Popen_safe, is_windows, commonpath from . import destdir_join parser = argparse.ArgumentParser() @@ -107,7 +107,7 @@ def build_gtkdoc(source_root, build_root # FIXME: Use mesonlib.File objects so we don't need to do this if not os.path.isabs(f): f = os.path.join(doc_src, f) - elif os.path.commonpath([f, build_root]) == build_root: + elif commonpath([f, build_root]) == build_root: continue shutil.copyfile(f, os.path.join(abs_out, os.path.basename(f))) --- a/run_meson_command_tests.py +++ b/run_meson_command_tests.py @@ -18,6 +18,7 @@ import os import tempfile import unittest import subprocess +import sys import zipapp from pathlib import Path @@ -73,13 +74,27 @@ class CommandTests(unittest.TestCase): # If this call hangs CI will just abort. It is very hard to distinguish # between CI issue and test bug in that case. Set timeout and fail loud # instead. - p = subprocess.run(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=os.environ.copy(), - universal_newlines=True, cwd=workdir, timeout=60 * 5) - print(p.stdout) - if p.returncode != 0: - raise subprocess.CalledProcessError(p.returncode, command) - return p.stdout + if sys.version_info >= (3, 5): + p = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, + cwd=workdir, timeout=60 * 5) + print(p.stdout) + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, command) + return p.stdout + else: + try: + po = subprocess.check_output(command, stderr=subprocess.STDOUT, + env=os.environ.copy(), + universal_newlines=True, + cwd=workdir, timeout=60 * 5) + except subprocess.CalledProcessError as e: + print(e.output) + raise subprocess.CalledProcessError(e.returncode, e.cmd) + else: + print(po) + return po def assertMesonCommandIs(self, line, cmd): self.assertTrue(line.startswith('meson_command '), msg=line) --- a/run_unittests.py +++ b/run_unittests.py @@ -21,6 +21,7 @@ import tempfile import textwrap import os import shutil +import sys import unittest import platform from itertools import chain @@ -892,6 +893,24 @@ class DataTests(unittest.TestCase): defined = set([a.strip() for a in res.group().split('\\')][1:]) self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) + def test_commonpath(self): + from os.path import sep + commonpath = mesonbuild.mesonlib.commonpath + self.assertRaises(ValueError, commonpath, []) + self.assertEqual(commonpath(['/usr', '/usr']), sep + 'usr') + self.assertEqual(commonpath(['/usr', '/usr/']), sep + 'usr') + self.assertEqual(commonpath(['/usr', '/usr/bin']), sep + 'usr') + self.assertEqual(commonpath(['/usr/', '/usr/bin']), sep + 'usr') + self.assertEqual(commonpath(['/usr/./', '/usr/bin']), sep + 'usr') + self.assertEqual(commonpath(['/usr/bin', '/usr/bin']), sep + 'usr' + sep + 'bin') + self.assertEqual(commonpath(['/usr//bin', '/usr/bin']), sep + 'usr' + sep + 'bin') + self.assertEqual(commonpath(['/usr/./bin', '/usr/bin']), sep + 'usr' + sep + 'bin') + self.assertEqual(commonpath(['/usr/local', '/usr/lib']), sep + 'usr') + self.assertEqual(commonpath(['/usr', '/bin']), sep) + prefix = '/some/path/to/prefix' + libdir = '/some/path/to/prefix/libdir' + self.assertEqual(commonpath([prefix, libdir]), str(PurePath(prefix))) + class BasePlatformTests(unittest.TestCase): def setUp(self): @@ -972,9 +991,16 @@ class BasePlatformTests(unittest.TestCas # If this call hangs CI will just abort. It is very hard to distinguish # between CI issue and test bug in that case. Set timeout and fail loud # instead. - p = subprocess.run(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=os.environ.copy(), - universal_newlines=True, cwd=workdir, timeout=60 * 5) + if sys.version_info >= (3, 5): + p = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, cwd=workdir, + timeout=60 * 5) + else: + p = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, cwd=workdir) + p.stdout = p.communicate(timeout=60 * 5)[0] print(p.stdout) if p.returncode != 0: if 'MESON_SKIP_TEST' in p.stdout: @@ -2698,17 +2724,23 @@ recommended as it is not supported on so of = open(mfile, 'w') of.write("project('foobar', 'c')\n") of.close() - pc = subprocess.run(self.setup_command, - cwd=srcdir, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) + if sys.version_info >= (3, 5): + pc = subprocess.run(self.setup_command, + cwd=srcdir, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + else: + pc = subprocess.Popen(self.setup_command, + cwd=srcdir, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + pc.stdout = pc.communicate()[0] self.assertIn(b'Must specify at least one directory name', pc.stdout) with tempfile.TemporaryDirectory(dir=srcdir) as builddir: - subprocess.run(self.setup_command, - check=True, - cwd=builddir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.check_call(self.setup_command, + cwd=builddir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) def get_opts_as_dict(self): result = {} --- a/setup.py +++ b/setup.py @@ -18,9 +18,9 @@ import sys from mesonbuild.coredata import version -if sys.version_info < (3, 5, 0): +if sys.version_info < (3, 4, 0): print('Tried to install with an unsupported version of Python. ' - 'Meson requires Python 3.5.0 or greater') + 'Meson requires Python 3.4.0 or greater') sys.exit(1) from setuptools import setup --- "a/test cases/common/188 find override/subdir/converter.py" +++ "b/test cases/common/188 find override/subdir/converter.py" @@ -10,6 +10,7 @@ ftempl = '''int %s() { } ''' -d = pathlib.Path(ifilename).read_text().split('\n')[0].strip() - -pathlib.Path(ofilename).write_text(ftempl % d) +with pathlib.Path(ifilename).open('r') as f: + d = f.readline().strip() +with pathlib.Path(ofilename).open('w') as f: + f.write(ftempl % d) --- "a/test cases/common/188 find override/subdir/gencodegen.py.in" +++ "b/test cases/common/188 find override/subdir/gencodegen.py.in" @@ -10,6 +10,7 @@ ftempl = '''int %s() { } ''' -d = pathlib.Path(ifilename).read_text().split('\n')[0].strip() - -pathlib.Path(ofilename).write_text(ftempl % d) +with pathlib.Path(ifilename).open('r') as f: + d = f.readline().strip() +with pathlib.Path(ofilename).open('w') as f: + f.write(ftempl % d) --- "a/test cases/unit/35 dist script/replacer.py" +++ "b/test cases/unit/35 dist script/replacer.py" @@ -7,6 +7,8 @@ source_root = pathlib.Path(os.environ['M modfile = source_root / 'prog.c' -contents = modfile.read_text() +with modfile.open('r') as f: + contents = f.read() contents = contents.replace('"incorrect"', '"correct"') -modfile.write_text(contents) +with modfile.open('w') as f: + f.write(contents) --- "a/test cases/windows/13 test argument extra paths/test/test_run_exe.py" +++ "b/test cases/windows/13 test argument extra paths/test/test_run_exe.py" @@ -7,6 +7,6 @@ if __name__ == '__main__': parser.add_argument('prog') args = parser.parse_args() - res = subprocess.run(args.prog) + ret = subprocess.call(args.prog) - sys.exit(res.returncode - 42) + sys.exit(ret - 42)