install.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. """distutils.command.install
  2. Implements the Distutils 'install' command."""
  3. from __future__ import annotations
  4. import collections
  5. import contextlib
  6. import itertools
  7. import os
  8. import sys
  9. import sysconfig
  10. from distutils._log import log
  11. from site import USER_BASE, USER_SITE
  12. from typing import ClassVar
  13. from ..core import Command
  14. from ..debug import DEBUG
  15. from ..errors import DistutilsOptionError, DistutilsPlatformError
  16. from ..file_util import write_file
  17. from ..sysconfig import get_config_vars
  18. from ..util import change_root, convert_path, get_platform, subst_vars
  19. from . import _framework_compat as fw
  20. HAS_USER_SITE = True
  21. WINDOWS_SCHEME = {
  22. 'purelib': '{base}/Lib/site-packages',
  23. 'platlib': '{base}/Lib/site-packages',
  24. 'headers': '{base}/Include/{dist_name}',
  25. 'scripts': '{base}/Scripts',
  26. 'data': '{base}',
  27. }
  28. INSTALL_SCHEMES = {
  29. 'posix_prefix': {
  30. 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages',
  31. 'platlib': '{platbase}/{platlibdir}/{implementation_lower}'
  32. '{py_version_short}/site-packages',
  33. 'headers': '{base}/include/{implementation_lower}'
  34. '{py_version_short}{abiflags}/{dist_name}',
  35. 'scripts': '{base}/bin',
  36. 'data': '{base}',
  37. },
  38. 'posix_home': {
  39. 'purelib': '{base}/lib/{implementation_lower}',
  40. 'platlib': '{base}/{platlibdir}/{implementation_lower}',
  41. 'headers': '{base}/include/{implementation_lower}/{dist_name}',
  42. 'scripts': '{base}/bin',
  43. 'data': '{base}',
  44. },
  45. 'nt': WINDOWS_SCHEME,
  46. 'pypy': {
  47. 'purelib': '{base}/site-packages',
  48. 'platlib': '{base}/site-packages',
  49. 'headers': '{base}/include/{dist_name}',
  50. 'scripts': '{base}/bin',
  51. 'data': '{base}',
  52. },
  53. 'pypy_nt': {
  54. 'purelib': '{base}/site-packages',
  55. 'platlib': '{base}/site-packages',
  56. 'headers': '{base}/include/{dist_name}',
  57. 'scripts': '{base}/Scripts',
  58. 'data': '{base}',
  59. },
  60. }
  61. # user site schemes
  62. if HAS_USER_SITE:
  63. INSTALL_SCHEMES['nt_user'] = {
  64. 'purelib': '{usersite}',
  65. 'platlib': '{usersite}',
  66. 'headers': '{userbase}/{implementation}{py_version_nodot_plat}'
  67. '/Include/{dist_name}',
  68. 'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
  69. 'data': '{userbase}',
  70. }
  71. INSTALL_SCHEMES['posix_user'] = {
  72. 'purelib': '{usersite}',
  73. 'platlib': '{usersite}',
  74. 'headers': '{userbase}/include/{implementation_lower}'
  75. '{py_version_short}{abiflags}/{dist_name}',
  76. 'scripts': '{userbase}/bin',
  77. 'data': '{userbase}',
  78. }
  79. INSTALL_SCHEMES.update(fw.schemes)
  80. # The keys to an installation scheme; if any new types of files are to be
  81. # installed, be sure to add an entry to every installation scheme above,
  82. # and to SCHEME_KEYS here.
  83. SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data')
  84. def _load_sysconfig_schemes():
  85. with contextlib.suppress(AttributeError):
  86. return {
  87. scheme: sysconfig.get_paths(scheme, expand=False)
  88. for scheme in sysconfig.get_scheme_names()
  89. }
  90. def _load_schemes():
  91. """
  92. Extend default schemes with schemes from sysconfig.
  93. """
  94. sysconfig_schemes = _load_sysconfig_schemes() or {}
  95. return {
  96. scheme: {
  97. **INSTALL_SCHEMES.get(scheme, {}),
  98. **sysconfig_schemes.get(scheme, {}),
  99. }
  100. for scheme in set(itertools.chain(INSTALL_SCHEMES, sysconfig_schemes))
  101. }
  102. def _get_implementation():
  103. if hasattr(sys, 'pypy_version_info'):
  104. return 'PyPy'
  105. else:
  106. return 'Python'
  107. def _select_scheme(ob, name):
  108. scheme = _inject_headers(name, _load_scheme(_resolve_scheme(name)))
  109. vars(ob).update(_remove_set(ob, _scheme_attrs(scheme)))
  110. def _remove_set(ob, attrs):
  111. """
  112. Include only attrs that are None in ob.
  113. """
  114. return {key: value for key, value in attrs.items() if getattr(ob, key) is None}
  115. def _resolve_scheme(name):
  116. os_name, sep, key = name.partition('_')
  117. try:
  118. resolved = sysconfig.get_preferred_scheme(key)
  119. except Exception:
  120. resolved = fw.scheme(name)
  121. return resolved
  122. def _load_scheme(name):
  123. return _load_schemes()[name]
  124. def _inject_headers(name, scheme):
  125. """
  126. Given a scheme name and the resolved scheme,
  127. if the scheme does not include headers, resolve
  128. the fallback scheme for the name and use headers
  129. from it. pypa/distutils#88
  130. """
  131. # Bypass the preferred scheme, which may not
  132. # have defined headers.
  133. fallback = _load_scheme(name)
  134. scheme.setdefault('headers', fallback['headers'])
  135. return scheme
  136. def _scheme_attrs(scheme):
  137. """Resolve install directories by applying the install schemes."""
  138. return {f'install_{key}': scheme[key] for key in SCHEME_KEYS}
  139. class install(Command):
  140. description = "install everything from build directory"
  141. user_options = [
  142. # Select installation scheme and set base director(y|ies)
  143. ('prefix=', None, "installation prefix"),
  144. ('exec-prefix=', None, "(Unix only) prefix for platform-specific files"),
  145. ('home=', None, "(Unix only) home directory to install under"),
  146. # Or, just set the base director(y|ies)
  147. (
  148. 'install-base=',
  149. None,
  150. "base installation directory (instead of --prefix or --home)",
  151. ),
  152. (
  153. 'install-platbase=',
  154. None,
  155. "base installation directory for platform-specific files (instead of --exec-prefix or --home)",
  156. ),
  157. ('root=', None, "install everything relative to this alternate root directory"),
  158. # Or, explicitly set the installation scheme
  159. (
  160. 'install-purelib=',
  161. None,
  162. "installation directory for pure Python module distributions",
  163. ),
  164. (
  165. 'install-platlib=',
  166. None,
  167. "installation directory for non-pure module distributions",
  168. ),
  169. (
  170. 'install-lib=',
  171. None,
  172. "installation directory for all module distributions (overrides --install-purelib and --install-platlib)",
  173. ),
  174. ('install-headers=', None, "installation directory for C/C++ headers"),
  175. ('install-scripts=', None, "installation directory for Python scripts"),
  176. ('install-data=', None, "installation directory for data files"),
  177. # Byte-compilation options -- see install_lib.py for details, as
  178. # these are duplicated from there (but only install_lib does
  179. # anything with them).
  180. ('compile', 'c', "compile .py to .pyc [default]"),
  181. ('no-compile', None, "don't compile .py files"),
  182. (
  183. 'optimize=',
  184. 'O',
  185. "also compile with optimization: -O1 for \"python -O\", "
  186. "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",
  187. ),
  188. # Miscellaneous control options
  189. ('force', 'f', "force installation (overwrite any existing files)"),
  190. ('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
  191. # Where to install documentation (eventually!)
  192. # ('doc-format=', None, "format of documentation to generate"),
  193. # ('install-man=', None, "directory for Unix man pages"),
  194. # ('install-html=', None, "directory for HTML documentation"),
  195. # ('install-info=', None, "directory for GNU info files"),
  196. ('record=', None, "filename in which to record list of installed files"),
  197. ]
  198. boolean_options: ClassVar[list[str]] = ['compile', 'force', 'skip-build']
  199. if HAS_USER_SITE:
  200. user_options.append((
  201. 'user',
  202. None,
  203. f"install in user site-package '{USER_SITE}'",
  204. ))
  205. boolean_options.append('user')
  206. negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}
  207. def initialize_options(self) -> None:
  208. """Initializes options."""
  209. # High-level options: these select both an installation base
  210. # and scheme.
  211. self.prefix: str | None = None
  212. self.exec_prefix: str | None = None
  213. self.home: str | None = None
  214. self.user = False
  215. # These select only the installation base; it's up to the user to
  216. # specify the installation scheme (currently, that means supplying
  217. # the --install-{platlib,purelib,scripts,data} options).
  218. self.install_base = None
  219. self.install_platbase = None
  220. self.root: str | None = None
  221. # These options are the actual installation directories; if not
  222. # supplied by the user, they are filled in using the installation
  223. # scheme implied by prefix/exec-prefix/home and the contents of
  224. # that installation scheme.
  225. self.install_purelib = None # for pure module distributions
  226. self.install_platlib = None # non-pure (dists w/ extensions)
  227. self.install_headers = None # for C/C++ headers
  228. self.install_lib: str | None = None # set to either purelib or platlib
  229. self.install_scripts = None
  230. self.install_data = None
  231. self.install_userbase = USER_BASE
  232. self.install_usersite = USER_SITE
  233. self.compile = None
  234. self.optimize = None
  235. # Deprecated
  236. # These two are for putting non-packagized distributions into their
  237. # own directory and creating a .pth file if it makes sense.
  238. # 'extra_path' comes from the setup file; 'install_path_file' can
  239. # be turned off if it makes no sense to install a .pth file. (But
  240. # better to install it uselessly than to guess wrong and not
  241. # install it when it's necessary and would be used!) Currently,
  242. # 'install_path_file' is always true unless some outsider meddles
  243. # with it.
  244. self.extra_path = None
  245. self.install_path_file = True
  246. # 'force' forces installation, even if target files are not
  247. # out-of-date. 'skip_build' skips running the "build" command,
  248. # handy if you know it's not necessary. 'warn_dir' (which is *not*
  249. # a user option, it's just there so the bdist_* commands can turn
  250. # it off) determines whether we warn about installing to a
  251. # directory not in sys.path.
  252. self.force = False
  253. self.skip_build = False
  254. self.warn_dir = True
  255. # These are only here as a conduit from the 'build' command to the
  256. # 'install_*' commands that do the real work. ('build_base' isn't
  257. # actually used anywhere, but it might be useful in future.) They
  258. # are not user options, because if the user told the install
  259. # command where the build directory is, that wouldn't affect the
  260. # build command.
  261. self.build_base = None
  262. self.build_lib = None
  263. # Not defined yet because we don't know anything about
  264. # documentation yet.
  265. # self.install_man = None
  266. # self.install_html = None
  267. # self.install_info = None
  268. self.record = None
  269. # -- Option finalizing methods -------------------------------------
  270. # (This is rather more involved than for most commands,
  271. # because this is where the policy for installing third-
  272. # party Python modules on various platforms given a wide
  273. # array of user input is decided. Yes, it's quite complex!)
  274. def finalize_options(self) -> None: # noqa: C901
  275. """Finalizes options."""
  276. # This method (and its helpers, like 'finalize_unix()',
  277. # 'finalize_other()', and 'select_scheme()') is where the default
  278. # installation directories for modules, extension modules, and
  279. # anything else we care to install from a Python module
  280. # distribution. Thus, this code makes a pretty important policy
  281. # statement about how third-party stuff is added to a Python
  282. # installation! Note that the actual work of installation is done
  283. # by the relatively simple 'install_*' commands; they just take
  284. # their orders from the installation directory options determined
  285. # here.
  286. # Check for errors/inconsistencies in the options; first, stuff
  287. # that's wrong on any platform.
  288. if (self.prefix or self.exec_prefix or self.home) and (
  289. self.install_base or self.install_platbase
  290. ):
  291. raise DistutilsOptionError(
  292. "must supply either prefix/exec-prefix/home or install-base/install-platbase -- not both"
  293. )
  294. if self.home and (self.prefix or self.exec_prefix):
  295. raise DistutilsOptionError(
  296. "must supply either home or prefix/exec-prefix -- not both"
  297. )
  298. if self.user and (
  299. self.prefix
  300. or self.exec_prefix
  301. or self.home
  302. or self.install_base
  303. or self.install_platbase
  304. ):
  305. raise DistutilsOptionError(
  306. "can't combine user with prefix, "
  307. "exec_prefix/home, or install_(plat)base"
  308. )
  309. # Next, stuff that's wrong (or dubious) only on certain platforms.
  310. if os.name != "posix":
  311. if self.exec_prefix:
  312. self.warn("exec-prefix option ignored on this platform")
  313. self.exec_prefix = None
  314. # Now the interesting logic -- so interesting that we farm it out
  315. # to other methods. The goal of these methods is to set the final
  316. # values for the install_{lib,scripts,data,...} options, using as
  317. # input a heady brew of prefix, exec_prefix, home, install_base,
  318. # install_platbase, user-supplied versions of
  319. # install_{purelib,platlib,lib,scripts,data,...}, and the
  320. # install schemes. Phew!
  321. self.dump_dirs("pre-finalize_{unix,other}")
  322. if os.name == 'posix':
  323. self.finalize_unix()
  324. else:
  325. self.finalize_other()
  326. self.dump_dirs("post-finalize_{unix,other}()")
  327. # Expand configuration variables, tilde, etc. in self.install_base
  328. # and self.install_platbase -- that way, we can use $base or
  329. # $platbase in the other installation directories and not worry
  330. # about needing recursive variable expansion (shudder).
  331. py_version = sys.version.split()[0]
  332. (prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix')
  333. try:
  334. abiflags = sys.abiflags
  335. except AttributeError:
  336. # sys.abiflags may not be defined on all platforms.
  337. abiflags = ''
  338. local_vars = {
  339. 'dist_name': self.distribution.get_name(),
  340. 'dist_version': self.distribution.get_version(),
  341. 'dist_fullname': self.distribution.get_fullname(),
  342. 'py_version': py_version,
  343. 'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
  344. 'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
  345. 'sys_prefix': prefix,
  346. 'prefix': prefix,
  347. 'sys_exec_prefix': exec_prefix,
  348. 'exec_prefix': exec_prefix,
  349. 'abiflags': abiflags,
  350. 'platlibdir': getattr(sys, 'platlibdir', 'lib'),
  351. 'implementation_lower': _get_implementation().lower(),
  352. 'implementation': _get_implementation(),
  353. }
  354. # vars for compatibility on older Pythons
  355. compat_vars = dict(
  356. # Python 3.9 and earlier
  357. py_version_nodot_plat=getattr(sys, 'winver', '').replace('.', ''),
  358. )
  359. if HAS_USER_SITE:
  360. local_vars['userbase'] = self.install_userbase
  361. local_vars['usersite'] = self.install_usersite
  362. self.config_vars = collections.ChainMap(
  363. local_vars,
  364. sysconfig.get_config_vars(),
  365. compat_vars,
  366. fw.vars(),
  367. )
  368. self.expand_basedirs()
  369. self.dump_dirs("post-expand_basedirs()")
  370. # Now define config vars for the base directories so we can expand
  371. # everything else.
  372. local_vars['base'] = self.install_base
  373. local_vars['platbase'] = self.install_platbase
  374. if DEBUG:
  375. from pprint import pprint
  376. print("config vars:")
  377. pprint(dict(self.config_vars))
  378. # Expand "~" and configuration variables in the installation
  379. # directories.
  380. self.expand_dirs()
  381. self.dump_dirs("post-expand_dirs()")
  382. # Create directories in the home dir:
  383. if self.user:
  384. self.create_home_path()
  385. # Pick the actual directory to install all modules to: either
  386. # install_purelib or install_platlib, depending on whether this
  387. # module distribution is pure or not. Of course, if the user
  388. # already specified install_lib, use their selection.
  389. if self.install_lib is None:
  390. if self.distribution.has_ext_modules(): # has extensions: non-pure
  391. self.install_lib = self.install_platlib
  392. else:
  393. self.install_lib = self.install_purelib
  394. # Convert directories from Unix /-separated syntax to the local
  395. # convention.
  396. self.convert_paths(
  397. 'lib',
  398. 'purelib',
  399. 'platlib',
  400. 'scripts',
  401. 'data',
  402. 'headers',
  403. 'userbase',
  404. 'usersite',
  405. )
  406. # Deprecated
  407. # Well, we're not actually fully completely finalized yet: we still
  408. # have to deal with 'extra_path', which is the hack for allowing
  409. # non-packagized module distributions (hello, Numerical Python!) to
  410. # get their own directories.
  411. self.handle_extra_path()
  412. self.install_libbase = self.install_lib # needed for .pth file
  413. self.install_lib = os.path.join(self.install_lib, self.extra_dirs)
  414. # If a new root directory was supplied, make all the installation
  415. # dirs relative to it.
  416. if self.root is not None:
  417. self.change_roots(
  418. 'libbase', 'lib', 'purelib', 'platlib', 'scripts', 'data', 'headers'
  419. )
  420. self.dump_dirs("after prepending root")
  421. # Find out the build directories, ie. where to install from.
  422. self.set_undefined_options(
  423. 'build', ('build_base', 'build_base'), ('build_lib', 'build_lib')
  424. )
  425. # Punt on doc directories for now -- after all, we're punting on
  426. # documentation completely!
  427. def dump_dirs(self, msg) -> None:
  428. """Dumps the list of user options."""
  429. if not DEBUG:
  430. return
  431. from ..fancy_getopt import longopt_xlate
  432. log.debug(msg + ":")
  433. for opt in self.user_options:
  434. opt_name = opt[0]
  435. if opt_name[-1] == "=":
  436. opt_name = opt_name[0:-1]
  437. if opt_name in self.negative_opt:
  438. opt_name = self.negative_opt[opt_name]
  439. opt_name = opt_name.translate(longopt_xlate)
  440. val = not getattr(self, opt_name)
  441. else:
  442. opt_name = opt_name.translate(longopt_xlate)
  443. val = getattr(self, opt_name)
  444. log.debug(" %s: %s", opt_name, val)
  445. def finalize_unix(self) -> None:
  446. """Finalizes options for posix platforms."""
  447. if self.install_base is not None or self.install_platbase is not None:
  448. incomplete_scheme = (
  449. (
  450. self.install_lib is None
  451. and self.install_purelib is None
  452. and self.install_platlib is None
  453. )
  454. or self.install_headers is None
  455. or self.install_scripts is None
  456. or self.install_data is None
  457. )
  458. if incomplete_scheme:
  459. raise DistutilsOptionError(
  460. "install-base or install-platbase supplied, but "
  461. "installation scheme is incomplete"
  462. )
  463. return
  464. if self.user:
  465. if self.install_userbase is None:
  466. raise DistutilsPlatformError("User base directory is not specified")
  467. self.install_base = self.install_platbase = self.install_userbase
  468. self.select_scheme("posix_user")
  469. elif self.home is not None:
  470. self.install_base = self.install_platbase = self.home
  471. self.select_scheme("posix_home")
  472. else:
  473. if self.prefix is None:
  474. if self.exec_prefix is not None:
  475. raise DistutilsOptionError(
  476. "must not supply exec-prefix without prefix"
  477. )
  478. # Allow Fedora to add components to the prefix
  479. _prefix_addition = getattr(sysconfig, '_prefix_addition', "")
  480. self.prefix = os.path.normpath(sys.prefix) + _prefix_addition
  481. self.exec_prefix = os.path.normpath(sys.exec_prefix) + _prefix_addition
  482. else:
  483. if self.exec_prefix is None:
  484. self.exec_prefix = self.prefix
  485. self.install_base = self.prefix
  486. self.install_platbase = self.exec_prefix
  487. self.select_scheme("posix_prefix")
  488. def finalize_other(self) -> None:
  489. """Finalizes options for non-posix platforms"""
  490. if self.user:
  491. if self.install_userbase is None:
  492. raise DistutilsPlatformError("User base directory is not specified")
  493. self.install_base = self.install_platbase = self.install_userbase
  494. self.select_scheme(os.name + "_user")
  495. elif self.home is not None:
  496. self.install_base = self.install_platbase = self.home
  497. self.select_scheme("posix_home")
  498. else:
  499. if self.prefix is None:
  500. self.prefix = os.path.normpath(sys.prefix)
  501. self.install_base = self.install_platbase = self.prefix
  502. try:
  503. self.select_scheme(os.name)
  504. except KeyError:
  505. raise DistutilsPlatformError(
  506. f"I don't know how to install stuff on '{os.name}'"
  507. )
  508. def select_scheme(self, name) -> None:
  509. _select_scheme(self, name)
  510. def _expand_attrs(self, attrs):
  511. for attr in attrs:
  512. val = getattr(self, attr)
  513. if val is not None:
  514. if os.name in ('posix', 'nt'):
  515. val = os.path.expanduser(val)
  516. val = subst_vars(val, self.config_vars)
  517. setattr(self, attr, val)
  518. def expand_basedirs(self) -> None:
  519. """Calls `os.path.expanduser` on install_base, install_platbase and
  520. root."""
  521. self._expand_attrs(['install_base', 'install_platbase', 'root'])
  522. def expand_dirs(self) -> None:
  523. """Calls `os.path.expanduser` on install dirs."""
  524. self._expand_attrs([
  525. 'install_purelib',
  526. 'install_platlib',
  527. 'install_lib',
  528. 'install_headers',
  529. 'install_scripts',
  530. 'install_data',
  531. ])
  532. def convert_paths(self, *names) -> None:
  533. """Call `convert_path` over `names`."""
  534. for name in names:
  535. attr = "install_" + name
  536. setattr(self, attr, convert_path(getattr(self, attr)))
  537. def handle_extra_path(self) -> None:
  538. """Set `path_file` and `extra_dirs` using `extra_path`."""
  539. if self.extra_path is None:
  540. self.extra_path = self.distribution.extra_path
  541. if self.extra_path is not None:
  542. log.warning(
  543. "Distribution option extra_path is deprecated. "
  544. "See issue27919 for details."
  545. )
  546. if isinstance(self.extra_path, str):
  547. self.extra_path = self.extra_path.split(',')
  548. if len(self.extra_path) == 1:
  549. path_file = extra_dirs = self.extra_path[0]
  550. elif len(self.extra_path) == 2:
  551. path_file, extra_dirs = self.extra_path
  552. else:
  553. raise DistutilsOptionError(
  554. "'extra_path' option must be a list, tuple, or "
  555. "comma-separated string with 1 or 2 elements"
  556. )
  557. # convert to local form in case Unix notation used (as it
  558. # should be in setup scripts)
  559. extra_dirs = convert_path(extra_dirs)
  560. else:
  561. path_file = None
  562. extra_dirs = ''
  563. # XXX should we warn if path_file and not extra_dirs? (in which
  564. # case the path file would be harmless but pointless)
  565. self.path_file = path_file
  566. self.extra_dirs = extra_dirs
  567. def change_roots(self, *names) -> None:
  568. """Change the install directories pointed by name using root."""
  569. for name in names:
  570. attr = "install_" + name
  571. setattr(self, attr, change_root(self.root, getattr(self, attr)))
  572. def create_home_path(self) -> None:
  573. """Create directories under ~."""
  574. if not self.user:
  575. return
  576. home = convert_path(os.path.expanduser("~"))
  577. for path in self.config_vars.values():
  578. if str(path).startswith(home) and not os.path.isdir(path):
  579. self.debug_print(f"os.makedirs('{path}', 0o700)")
  580. os.makedirs(path, 0o700)
  581. # -- Command execution methods -------------------------------------
  582. def run(self):
  583. """Runs the command."""
  584. # Obviously have to build before we can install
  585. if not self.skip_build:
  586. self.run_command('build')
  587. # If we built for any other platform, we can't install.
  588. build_plat = self.distribution.get_command_obj('build').plat_name
  589. # check warn_dir - it is a clue that the 'install' is happening
  590. # internally, and not to sys.path, so we don't check the platform
  591. # matches what we are running.
  592. if self.warn_dir and build_plat != get_platform():
  593. raise DistutilsPlatformError("Can't install when cross-compiling")
  594. # Run all sub-commands (at least those that need to be run)
  595. for cmd_name in self.get_sub_commands():
  596. self.run_command(cmd_name)
  597. if self.path_file:
  598. self.create_path_file()
  599. # write list of installed files, if requested.
  600. if self.record:
  601. outputs = self.get_outputs()
  602. if self.root: # strip any package prefix
  603. root_len = len(self.root)
  604. for counter in range(len(outputs)):
  605. outputs[counter] = outputs[counter][root_len:]
  606. self.execute(
  607. write_file,
  608. (self.record, outputs),
  609. f"writing list of installed files to '{self.record}'",
  610. )
  611. sys_path = map(os.path.normpath, sys.path)
  612. sys_path = map(os.path.normcase, sys_path)
  613. install_lib = os.path.normcase(os.path.normpath(self.install_lib))
  614. if (
  615. self.warn_dir
  616. and not (self.path_file and self.install_path_file)
  617. and install_lib not in sys_path
  618. ):
  619. log.debug(
  620. (
  621. "modules installed to '%s', which is not in "
  622. "Python's module search path (sys.path) -- "
  623. "you'll have to change the search path yourself"
  624. ),
  625. self.install_lib,
  626. )
  627. def create_path_file(self):
  628. """Creates the .pth file"""
  629. filename = os.path.join(self.install_libbase, self.path_file + ".pth")
  630. if self.install_path_file:
  631. self.execute(
  632. write_file, (filename, [self.extra_dirs]), f"creating {filename}"
  633. )
  634. else:
  635. self.warn(f"path file '{filename}' not created")
  636. # -- Reporting methods ---------------------------------------------
  637. def get_outputs(self):
  638. """Assembles the outputs of all the sub-commands."""
  639. outputs = []
  640. for cmd_name in self.get_sub_commands():
  641. cmd = self.get_finalized_command(cmd_name)
  642. # Add the contents of cmd.get_outputs(), ensuring
  643. # that outputs doesn't contain duplicate entries
  644. for filename in cmd.get_outputs():
  645. if filename not in outputs:
  646. outputs.append(filename)
  647. if self.path_file and self.install_path_file:
  648. outputs.append(os.path.join(self.install_libbase, self.path_file + ".pth"))
  649. return outputs
  650. def get_inputs(self):
  651. """Returns the inputs of all the sub-commands"""
  652. # XXX gee, this looks familiar ;-(
  653. inputs = []
  654. for cmd_name in self.get_sub_commands():
  655. cmd = self.get_finalized_command(cmd_name)
  656. inputs.extend(cmd.get_inputs())
  657. return inputs
  658. # -- Predicates for sub-command list -------------------------------
  659. def has_lib(self):
  660. """Returns true if the current distribution has any Python
  661. modules to install."""
  662. return (
  663. self.distribution.has_pure_modules() or self.distribution.has_ext_modules()
  664. )
  665. def has_headers(self):
  666. """Returns true if the current distribution has any headers to
  667. install."""
  668. return self.distribution.has_headers()
  669. def has_scripts(self):
  670. """Returns true if the current distribution has any scripts to.
  671. install."""
  672. return self.distribution.has_scripts()
  673. def has_data(self):
  674. """Returns true if the current distribution has any data to.
  675. install."""
  676. return self.distribution.has_data_files()
  677. # 'sub_commands': a list of commands this command might have to run to
  678. # get its work done. See cmd.py for more info.
  679. sub_commands = [
  680. ('install_lib', has_lib),
  681. ('install_headers', has_headers),
  682. ('install_scripts', has_scripts),
  683. ('install_data', has_data),
  684. ('install_egg_info', lambda self: True),
  685. ]