dist_info.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """
  2. Create a dist_info directory
  3. As defined in the wheel specification
  4. """
  5. import os
  6. import re
  7. import shutil
  8. import sys
  9. import warnings
  10. from contextlib import contextmanager
  11. from inspect import cleandoc
  12. from pathlib import Path
  13. from distutils.core import Command
  14. from distutils import log
  15. from setuptools.extern import packaging
  16. from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
  17. class dist_info(Command):
  18. description = 'create a .dist-info directory'
  19. user_options = [
  20. ('egg-base=', 'e', "directory containing .egg-info directories"
  21. " (default: top of the source tree)"
  22. " DEPRECATED: use --output-dir."),
  23. ('output-dir=', 'o', "directory inside of which the .dist-info will be"
  24. "created (default: top of the source tree)"),
  25. ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
  26. ('tag-build=', 'b', "Specify explicit tag to add to version number"),
  27. ('no-date', 'D', "Don't include date stamp [default]"),
  28. ('keep-egg-info', None, "*TRANSITIONAL* will be removed in the future"),
  29. ]
  30. boolean_options = ['tag-date', 'keep-egg-info']
  31. negative_opt = {'no-date': 'tag-date'}
  32. def initialize_options(self):
  33. self.egg_base = None
  34. self.output_dir = None
  35. self.name = None
  36. self.dist_info_dir = None
  37. self.tag_date = None
  38. self.tag_build = None
  39. self.keep_egg_info = False
  40. def finalize_options(self):
  41. if self.egg_base:
  42. msg = "--egg-base is deprecated for dist_info command. Use --output-dir."
  43. warnings.warn(msg, SetuptoolsDeprecationWarning)
  44. self.output_dir = self.egg_base or self.output_dir
  45. dist = self.distribution
  46. project_dir = dist.src_root or os.curdir
  47. self.output_dir = Path(self.output_dir or project_dir)
  48. egg_info = self.reinitialize_command("egg_info")
  49. egg_info.egg_base = str(self.output_dir)
  50. if self.tag_date:
  51. egg_info.tag_date = self.tag_date
  52. else:
  53. self.tag_date = egg_info.tag_date
  54. if self.tag_build:
  55. egg_info.tag_build = self.tag_build
  56. else:
  57. self.tag_build = egg_info.tag_build
  58. egg_info.finalize_options()
  59. self.egg_info = egg_info
  60. name = _safe(dist.get_name())
  61. version = _version(dist.get_version())
  62. self.name = f"{name}-{version}"
  63. self.dist_info_dir = os.path.join(self.output_dir, f"{self.name}.dist-info")
  64. @contextmanager
  65. def _maybe_bkp_dir(self, dir_path: str, requires_bkp: bool):
  66. if requires_bkp:
  67. bkp_name = f"{dir_path}.__bkp__"
  68. _rm(bkp_name, ignore_errors=True)
  69. _copy(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True)
  70. try:
  71. yield
  72. finally:
  73. _rm(dir_path, ignore_errors=True)
  74. shutil.move(bkp_name, dir_path)
  75. else:
  76. yield
  77. def run(self):
  78. self.output_dir.mkdir(parents=True, exist_ok=True)
  79. self.egg_info.run()
  80. egg_info_dir = self.egg_info.egg_info
  81. assert os.path.isdir(egg_info_dir), ".egg-info dir should have been created"
  82. log.info("creating '{}'".format(os.path.abspath(self.dist_info_dir)))
  83. bdist_wheel = self.get_finalized_command('bdist_wheel')
  84. # TODO: if bdist_wheel if merged into setuptools, just add "keep_egg_info" there
  85. with self._maybe_bkp_dir(egg_info_dir, self.keep_egg_info):
  86. bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir)
  87. def _safe(component: str) -> str:
  88. """Escape a component used to form a wheel name according to PEP 491"""
  89. return re.sub(r"[^\w\d.]+", "_", component)
  90. def _version(version: str) -> str:
  91. """Convert an arbitrary string to a version string."""
  92. v = version.replace(' ', '.')
  93. try:
  94. return str(packaging.version.Version(v)).replace("-", "_")
  95. except packaging.version.InvalidVersion:
  96. msg = f"""Invalid version: {version!r}.
  97. !!\n\n
  98. ###################
  99. # Invalid version #
  100. ###################
  101. {version!r} is not valid according to PEP 440.\n
  102. Please make sure specify a valid version for your package.
  103. Also note that future releases of setuptools may halt the build process
  104. if an invalid version is given.
  105. \n\n!!
  106. """
  107. warnings.warn(cleandoc(msg))
  108. return _safe(v).strip("_")
  109. def _rm(dir_name, **opts):
  110. if os.path.isdir(dir_name):
  111. shutil.rmtree(dir_name, **opts)
  112. def _copy(src, dst, **opts):
  113. if sys.version_info < (3, 8):
  114. opts.pop("dirs_exist_ok", None)
  115. shutil.copytree(src, dst, **opts)