1#!/usr/bin/env python3 2 3# Copyright 2018 The Meson development team 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import os 18import tempfile 19import unittest 20import subprocess 21import zipapp 22from pathlib import Path 23 24from mesonbuild.mesonlib import windows_proof_rmtree, python_command, is_windows 25from mesonbuild.coredata import version as meson_version 26 27 28def get_pypath(): 29 import sysconfig 30 pypath = sysconfig.get_path('purelib', vars={'base': ''}) 31 # Ensure that / is the path separator and not \, then strip / 32 return Path(pypath).as_posix().strip('/') 33 34def get_pybindir(): 35 import sysconfig 36 # 'Scripts' on Windows and 'bin' on other platforms including MSYS 37 return sysconfig.get_path('scripts', vars={'base': ''}).strip('\\/') 38 39class CommandTests(unittest.TestCase): 40 ''' 41 Test that running meson in various ways works as expected by checking the 42 value of mesonlib.meson_command that was set during configuration. 43 ''' 44 45 def setUp(self): 46 super().setUp() 47 self.orig_env = os.environ.copy() 48 self.orig_dir = os.getcwd() 49 os.environ['MESON_COMMAND_TESTS'] = '1' 50 self.tmpdir = Path(tempfile.mkdtemp()).resolve() 51 self.src_root = Path(__file__).resolve().parent 52 self.testdir = str(self.src_root / 'test cases/common/1 trivial') 53 self.meson_args = ['--backend=ninja'] 54 55 def tearDown(self): 56 try: 57 windows_proof_rmtree(str(self.tmpdir)) 58 except FileNotFoundError: 59 pass 60 os.environ.clear() 61 os.environ.update(self.orig_env) 62 os.chdir(str(self.orig_dir)) 63 super().tearDown() 64 65 def _run(self, command, workdir=None): 66 ''' 67 Run a command while printing the stdout, and also return a copy of it 68 ''' 69 # If this call hangs CI will just abort. It is very hard to distinguish 70 # between CI issue and test bug in that case. Set timeout and fail loud 71 # instead. 72 p = subprocess.run(command, stdout=subprocess.PIPE, 73 env=os.environ.copy(), universal_newlines=True, 74 cwd=workdir, timeout=60 * 5) 75 print(p.stdout) 76 if p.returncode != 0: 77 raise subprocess.CalledProcessError(p.returncode, command) 78 return p.stdout 79 80 def assertMesonCommandIs(self, line, cmd): 81 self.assertTrue(line.startswith('meson_command '), msg=line) 82 self.assertEqual(line, f'meson_command is {cmd!r}') 83 84 def test_meson_uninstalled(self): 85 # This is what the meson command must be for all these cases 86 resolved_meson_command = python_command + [str(self.src_root / 'meson.py')] 87 # Absolute path to meson.py 88 os.chdir('/') 89 builddir = str(self.tmpdir / 'build1') 90 meson_py = str(self.src_root / 'meson.py') 91 meson_setup = [meson_py, 'setup'] 92 meson_command = python_command + meson_setup + self.meson_args 93 stdo = self._run(meson_command + [self.testdir, builddir]) 94 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 95 # ./meson.py 96 os.chdir(str(self.src_root)) 97 builddir = str(self.tmpdir / 'build2') 98 meson_py = './meson.py' 99 meson_setup = [meson_py, 'setup'] 100 meson_command = python_command + meson_setup + self.meson_args 101 stdo = self._run(meson_command + [self.testdir, builddir]) 102 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 103 # Symlink to meson.py 104 if is_windows(): 105 # Symlinks require admin perms 106 return 107 os.chdir(str(self.src_root)) 108 builddir = str(self.tmpdir / 'build3') 109 # Create a symlink to meson.py in bindir, and add it to PATH 110 bindir = (self.tmpdir / 'bin') 111 bindir.mkdir() 112 (bindir / 'meson').symlink_to(self.src_root / 'meson.py') 113 os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] 114 # See if it works! 115 meson_py = 'meson' 116 meson_setup = [meson_py, 'setup'] 117 meson_command = meson_setup + self.meson_args 118 stdo = self._run(meson_command + [self.testdir, builddir]) 119 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 120 121 def test_meson_installed(self): 122 # Install meson 123 prefix = self.tmpdir / 'prefix' 124 pylibdir = prefix / get_pypath() 125 bindir = prefix / get_pybindir() 126 pylibdir.mkdir(parents=True) 127 # XXX: join with empty name so it always ends with os.sep otherwise 128 # distutils complains that prefix isn't contained in PYTHONPATH 129 os.environ['PYTHONPATH'] = os.path.join(str(pylibdir), '') 130 os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] 131 self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) 132 # Fix importlib-metadata by appending all dirs in pylibdir 133 PYTHONPATHS = [pylibdir] + [x for x in pylibdir.iterdir()] 134 PYTHONPATHS = [os.path.join(str(x), '') for x in PYTHONPATHS] 135 os.environ['PYTHONPATH'] = os.pathsep.join(PYTHONPATHS) 136 # Check that all the files were installed correctly 137 self.assertTrue(bindir.is_dir()) 138 self.assertTrue(pylibdir.is_dir()) 139 # Run `meson` 140 os.chdir('/') 141 resolved_meson_command = [str(bindir / 'meson')] 142 builddir = str(self.tmpdir / 'build1') 143 meson_setup = ['meson', 'setup'] 144 meson_command = meson_setup + self.meson_args 145 stdo = self._run(meson_command + [self.testdir, builddir]) 146 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 147 # Run `/path/to/meson` 148 builddir = str(self.tmpdir / 'build2') 149 meson_setup = [str(bindir / 'meson'), 'setup'] 150 meson_command = meson_setup + self.meson_args 151 stdo = self._run(meson_command + [self.testdir, builddir]) 152 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 153 # Run `python3 -m mesonbuild.mesonmain` 154 resolved_meson_command = python_command + ['-m', 'mesonbuild.mesonmain'] 155 builddir = str(self.tmpdir / 'build3') 156 meson_setup = ['-m', 'mesonbuild.mesonmain', 'setup'] 157 meson_command = python_command + meson_setup + self.meson_args 158 stdo = self._run(meson_command + [self.testdir, builddir]) 159 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 160 if is_windows(): 161 # Next part requires a shell 162 return 163 # `meson` is a wrapper to `meson.real` 164 resolved_meson_command = [str(bindir / 'meson.real')] 165 builddir = str(self.tmpdir / 'build4') 166 (bindir / 'meson').rename(bindir / 'meson.real') 167 wrapper = (bindir / 'meson') 168 wrapper.write_text('#!/bin/sh\n\nmeson.real "$@"', encoding='utf-8') 169 wrapper.chmod(0o755) 170 meson_setup = [str(wrapper), 'setup'] 171 meson_command = meson_setup + self.meson_args 172 stdo = self._run(meson_command + [self.testdir, builddir]) 173 self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) 174 175 def test_meson_exe_windows(self): 176 raise unittest.SkipTest('NOT IMPLEMENTED') 177 178 def test_meson_zipapp(self): 179 if is_windows(): 180 raise unittest.SkipTest('NOT IMPLEMENTED') 181 source = Path(__file__).resolve().parent 182 target = self.tmpdir / 'meson.pyz' 183 script = source / 'packaging' / 'create_zipapp.py' 184 self._run([script.as_posix(), source, '--outfile', target, '--interpreter', python_command[0]]) 185 self._run([target.as_posix(), '--help']) 186 187 188if __name__ == '__main__': 189 print('Meson build system', meson_version, 'Command Tests') 190 raise SystemExit(unittest.main(buffer=True)) 191