run_meson_command_tests.py 9.1 KB

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