test_install.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. """Tests for distutils.command.install."""
  2. import logging
  3. import os
  4. import pathlib
  5. import site
  6. import sys
  7. from distutils import sysconfig
  8. from distutils.command import install as install_module
  9. from distutils.command.build_ext import build_ext
  10. from distutils.command.install import INSTALL_SCHEMES, install
  11. from distutils.core import Distribution
  12. from distutils.errors import DistutilsOptionError
  13. from distutils.extension import Extension
  14. from distutils.tests import missing_compiler_executable, support
  15. from distutils.util import is_mingw
  16. import pytest
  17. def _make_ext_name(modname):
  18. return modname + sysconfig.get_config_var('EXT_SUFFIX')
  19. @support.combine_markers
  20. @pytest.mark.usefixtures('save_env')
  21. class TestInstall(
  22. support.TempdirManager,
  23. ):
  24. @pytest.mark.xfail(
  25. 'platform.system() == "Windows" and sys.version_info > (3, 11)',
  26. reason="pypa/distutils#148",
  27. )
  28. def test_home_installation_scheme(self):
  29. # This ensure two things:
  30. # - that --home generates the desired set of directory names
  31. # - test --home is supported on all platforms
  32. builddir = self.mkdtemp()
  33. destination = os.path.join(builddir, "installation")
  34. dist = Distribution({"name": "foopkg"})
  35. # script_name need not exist, it just need to be initialized
  36. dist.script_name = os.path.join(builddir, "setup.py")
  37. dist.command_obj["build"] = support.DummyCommand(
  38. build_base=builddir,
  39. build_lib=os.path.join(builddir, "lib"),
  40. )
  41. cmd = install(dist)
  42. cmd.home = destination
  43. cmd.ensure_finalized()
  44. assert cmd.install_base == destination
  45. assert cmd.install_platbase == destination
  46. def check_path(got, expected):
  47. got = os.path.normpath(got)
  48. expected = os.path.normpath(expected)
  49. assert got == expected
  50. impl_name = sys.implementation.name.replace("cpython", "python")
  51. libdir = os.path.join(destination, "lib", impl_name)
  52. check_path(cmd.install_lib, libdir)
  53. _platlibdir = getattr(sys, "platlibdir", "lib")
  54. platlibdir = os.path.join(destination, _platlibdir, impl_name)
  55. check_path(cmd.install_platlib, platlibdir)
  56. check_path(cmd.install_purelib, libdir)
  57. check_path(
  58. cmd.install_headers,
  59. os.path.join(destination, "include", impl_name, "foopkg"),
  60. )
  61. check_path(cmd.install_scripts, os.path.join(destination, "bin"))
  62. check_path(cmd.install_data, destination)
  63. def test_user_site(self, monkeypatch):
  64. # test install with --user
  65. # preparing the environment for the test
  66. self.tmpdir = self.mkdtemp()
  67. orig_site = site.USER_SITE
  68. orig_base = site.USER_BASE
  69. monkeypatch.setattr(site, 'USER_BASE', os.path.join(self.tmpdir, 'B'))
  70. monkeypatch.setattr(site, 'USER_SITE', os.path.join(self.tmpdir, 'S'))
  71. monkeypatch.setattr(install_module, 'USER_BASE', site.USER_BASE)
  72. monkeypatch.setattr(install_module, 'USER_SITE', site.USER_SITE)
  73. def _expanduser(path):
  74. if path.startswith('~'):
  75. return os.path.normpath(self.tmpdir + path[1:])
  76. return path
  77. monkeypatch.setattr(os.path, 'expanduser', _expanduser)
  78. for key in ('nt_user', 'posix_user'):
  79. assert key in INSTALL_SCHEMES
  80. dist = Distribution({'name': 'xx'})
  81. cmd = install(dist)
  82. # making sure the user option is there
  83. options = [name for name, short, label in cmd.user_options]
  84. assert 'user' in options
  85. # setting a value
  86. cmd.user = True
  87. # user base and site shouldn't be created yet
  88. assert not os.path.exists(site.USER_BASE)
  89. assert not os.path.exists(site.USER_SITE)
  90. # let's run finalize
  91. cmd.ensure_finalized()
  92. # now they should
  93. assert os.path.exists(site.USER_BASE)
  94. assert os.path.exists(site.USER_SITE)
  95. assert 'userbase' in cmd.config_vars
  96. assert 'usersite' in cmd.config_vars
  97. actual_headers = os.path.relpath(cmd.install_headers, site.USER_BASE)
  98. if os.name == 'nt' and not is_mingw():
  99. site_path = os.path.relpath(os.path.dirname(orig_site), orig_base)
  100. include = os.path.join(site_path, 'Include')
  101. else:
  102. include = sysconfig.get_python_inc(0, '')
  103. expect_headers = os.path.join(include, 'xx')
  104. assert os.path.normcase(actual_headers) == os.path.normcase(expect_headers)
  105. def test_handle_extra_path(self):
  106. dist = Distribution({'name': 'xx', 'extra_path': 'path,dirs'})
  107. cmd = install(dist)
  108. # two elements
  109. cmd.handle_extra_path()
  110. assert cmd.extra_path == ['path', 'dirs']
  111. assert cmd.extra_dirs == 'dirs'
  112. assert cmd.path_file == 'path'
  113. # one element
  114. cmd.extra_path = ['path']
  115. cmd.handle_extra_path()
  116. assert cmd.extra_path == ['path']
  117. assert cmd.extra_dirs == 'path'
  118. assert cmd.path_file == 'path'
  119. # none
  120. dist.extra_path = cmd.extra_path = None
  121. cmd.handle_extra_path()
  122. assert cmd.extra_path is None
  123. assert cmd.extra_dirs == ''
  124. assert cmd.path_file is None
  125. # three elements (no way !)
  126. cmd.extra_path = 'path,dirs,again'
  127. with pytest.raises(DistutilsOptionError):
  128. cmd.handle_extra_path()
  129. def test_finalize_options(self):
  130. dist = Distribution({'name': 'xx'})
  131. cmd = install(dist)
  132. # must supply either prefix/exec-prefix/home or
  133. # install-base/install-platbase -- not both
  134. cmd.prefix = 'prefix'
  135. cmd.install_base = 'base'
  136. with pytest.raises(DistutilsOptionError):
  137. cmd.finalize_options()
  138. # must supply either home or prefix/exec-prefix -- not both
  139. cmd.install_base = None
  140. cmd.home = 'home'
  141. with pytest.raises(DistutilsOptionError):
  142. cmd.finalize_options()
  143. # can't combine user with prefix/exec_prefix/home or
  144. # install_(plat)base
  145. cmd.prefix = None
  146. cmd.user = 'user'
  147. with pytest.raises(DistutilsOptionError):
  148. cmd.finalize_options()
  149. def test_record(self):
  150. install_dir = self.mkdtemp()
  151. project_dir, dist = self.create_dist(py_modules=['hello'], scripts=['sayhi'])
  152. os.chdir(project_dir)
  153. self.write_file('hello.py', "def main(): print('o hai')")
  154. self.write_file('sayhi', 'from hello import main; main()')
  155. cmd = install(dist)
  156. dist.command_obj['install'] = cmd
  157. cmd.root = install_dir
  158. cmd.record = os.path.join(project_dir, 'filelist')
  159. cmd.ensure_finalized()
  160. cmd.run()
  161. content = pathlib.Path(cmd.record).read_text(encoding='utf-8')
  162. found = [pathlib.Path(line).name for line in content.splitlines()]
  163. expected = [
  164. 'hello.py',
  165. f'hello.{sys.implementation.cache_tag}.pyc',
  166. 'sayhi',
  167. 'UNKNOWN-0.0.0-py{}.{}.egg-info'.format(*sys.version_info[:2]),
  168. ]
  169. assert found == expected
  170. def test_record_extensions(self):
  171. cmd = missing_compiler_executable()
  172. if cmd is not None:
  173. pytest.skip(f'The {cmd!r} command is not found')
  174. install_dir = self.mkdtemp()
  175. project_dir, dist = self.create_dist(
  176. ext_modules=[Extension('xx', ['xxmodule.c'])]
  177. )
  178. os.chdir(project_dir)
  179. support.copy_xxmodule_c(project_dir)
  180. buildextcmd = build_ext(dist)
  181. support.fixup_build_ext(buildextcmd)
  182. buildextcmd.ensure_finalized()
  183. cmd = install(dist)
  184. dist.command_obj['install'] = cmd
  185. dist.command_obj['build_ext'] = buildextcmd
  186. cmd.root = install_dir
  187. cmd.record = os.path.join(project_dir, 'filelist')
  188. cmd.ensure_finalized()
  189. cmd.run()
  190. content = pathlib.Path(cmd.record).read_text(encoding='utf-8')
  191. found = [pathlib.Path(line).name for line in content.splitlines()]
  192. expected = [
  193. _make_ext_name('xx'),
  194. 'UNKNOWN-0.0.0-py{}.{}.egg-info'.format(*sys.version_info[:2]),
  195. ]
  196. assert found == expected
  197. def test_debug_mode(self, caplog, monkeypatch):
  198. # this covers the code called when DEBUG is set
  199. monkeypatch.setattr(install_module, 'DEBUG', True)
  200. caplog.set_level(logging.DEBUG)
  201. self.test_record()
  202. assert any(rec for rec in caplog.records if rec.levelno == logging.DEBUG)