--- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -31,7 +31,7 @@ from .. import compilers from ..compilers import CompilerArgs, CCompiler, get_macos_dylib_install_name 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, InstallData, TargetInstallData from ..build import InvalidArguments @@ -1209,8 +1209,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 @@ -17,7 +17,7 @@ import pickle, os, uuid, shlex import sys from pathlib import PurePath from collections import OrderedDict -from .mesonlib import MesonException +from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix import ast import argparse @@ -300,7 +300,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/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -1069,6 +1069,30 @@ def detect_subprojects(spdir_name, curre def get_error_location_string(fname, lineno): return '{}:{}:'.format(fname, lineno) +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.MutableSet): """A set that preserves the order in which items are added, by first insertion. --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -245,8 +245,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/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/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 from unittest import mock from configparser import ConfigParser @@ -689,6 +690,24 @@ class InternalTests(unittest.TestCase): PkgConfigDependency.pkgbin_cache = {} PkgConfigDependency.class_pkgbin = None + 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): @@ -769,9 +788,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: @@ -2497,17 +2523,30 @@ 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) + if sys.version_info >= (3, 5): + subprocess.run(self.setup_command, + check=True, + cwd=builddir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + else: + subprocess.check_call(self.setup_command, + cwd=builddir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) class FailureTests(BasePlatformTests): ''' --- a/setup.py +++ b/setup.py @@ -19,9 +19,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) # We need to support Python installations that have nothing but the basic --- "a/test cases/common/193 find override/subdir/converter.py" +++ "b/test cases/common/193 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().split('\n')[0].strip() +with pathlib.Path(ofilename).open('w') as f: + f.write(ftempl % d) --- "a/test cases/common/193 find override/subdir/gencodegen.py.in" +++ "b/test cases/common/193 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().split('\n')[0].strip() +with pathlib.Path(ofilename).open('w') as f: + f.write(ftempl % d) --- "a/test cases/windows/14 test argument extra paths/test/test_run_exe.py" +++ "b/test cases/windows/14 test argument extra paths/test/test_run_exe.py" @@ -7,6 +7,9 @@ if __name__ == '__main__': parser.add_argument('prog') args = parser.parse_args() - res = subprocess.run(args.prog) + if sys.version_info >= (3, 5): + res = subprocess.run(args.prog) + else: + res = subprocess.call(args.prog) sys.exit(res.returncode - 42)