Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

egg_info.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. """setuptools.command.egg_info
  2. Create a distribution's .egg-info directory and contents"""
  3. from distutils.filelist import FileList as _FileList
  4. from distutils.errors import DistutilsInternalError
  5. from distutils.util import convert_path
  6. from distutils import log
  7. import distutils.errors
  8. import distutils.filelist
  9. import os
  10. import re
  11. import sys
  12. import io
  13. import warnings
  14. import time
  15. import collections
  16. from setuptools.extern import six
  17. from setuptools.extern.six.moves import map
  18. from setuptools import Command
  19. from setuptools.command.sdist import sdist
  20. from setuptools.command.sdist import walk_revctrl
  21. from setuptools.command.setopt import edit_config
  22. from setuptools.command import bdist_egg
  23. from pkg_resources import (
  24. parse_requirements, safe_name, parse_version,
  25. safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
  26. import setuptools.unicode_utils as unicode_utils
  27. from setuptools.glob import glob
  28. from setuptools.extern import packaging
  29. def translate_pattern(glob):
  30. """
  31. Translate a file path glob like '*.txt' in to a regular expression.
  32. This differs from fnmatch.translate which allows wildcards to match
  33. directory separators. It also knows about '**/' which matches any number of
  34. directories.
  35. """
  36. pat = ''
  37. # This will split on '/' within [character classes]. This is deliberate.
  38. chunks = glob.split(os.path.sep)
  39. sep = re.escape(os.sep)
  40. valid_char = '[^%s]' % (sep,)
  41. for c, chunk in enumerate(chunks):
  42. last_chunk = c == len(chunks) - 1
  43. # Chunks that are a literal ** are globstars. They match anything.
  44. if chunk == '**':
  45. if last_chunk:
  46. # Match anything if this is the last component
  47. pat += '.*'
  48. else:
  49. # Match '(name/)*'
  50. pat += '(?:%s+%s)*' % (valid_char, sep)
  51. continue # Break here as the whole path component has been handled
  52. # Find any special characters in the remainder
  53. i = 0
  54. chunk_len = len(chunk)
  55. while i < chunk_len:
  56. char = chunk[i]
  57. if char == '*':
  58. # Match any number of name characters
  59. pat += valid_char + '*'
  60. elif char == '?':
  61. # Match a name character
  62. pat += valid_char
  63. elif char == '[':
  64. # Character class
  65. inner_i = i + 1
  66. # Skip initial !/] chars
  67. if inner_i < chunk_len and chunk[inner_i] == '!':
  68. inner_i = inner_i + 1
  69. if inner_i < chunk_len and chunk[inner_i] == ']':
  70. inner_i = inner_i + 1
  71. # Loop till the closing ] is found
  72. while inner_i < chunk_len and chunk[inner_i] != ']':
  73. inner_i = inner_i + 1
  74. if inner_i >= chunk_len:
  75. # Got to the end of the string without finding a closing ]
  76. # Do not treat this as a matching group, but as a literal [
  77. pat += re.escape(char)
  78. else:
  79. # Grab the insides of the [brackets]
  80. inner = chunk[i + 1:inner_i]
  81. char_class = ''
  82. # Class negation
  83. if inner[0] == '!':
  84. char_class = '^'
  85. inner = inner[1:]
  86. char_class += re.escape(inner)
  87. pat += '[%s]' % (char_class,)
  88. # Skip to the end ]
  89. i = inner_i
  90. else:
  91. pat += re.escape(char)
  92. i += 1
  93. # Join each chunk with the dir separator
  94. if not last_chunk:
  95. pat += sep
  96. pat += r'\Z'
  97. return re.compile(pat, flags=re.MULTILINE|re.DOTALL)
  98. class InfoCommon:
  99. tag_build = None
  100. tag_date = None
  101. @property
  102. def name(self):
  103. return safe_name(self.distribution.get_name())
  104. def tagged_version(self):
  105. version = self.distribution.get_version()
  106. # egg_info may be called more than once for a distribution,
  107. # in which case the version string already contains all tags.
  108. if self.vtags and version.endswith(self.vtags):
  109. return safe_version(version)
  110. return safe_version(version + self.vtags)
  111. def tags(self):
  112. version = ''
  113. if self.tag_build:
  114. version += self.tag_build
  115. if self.tag_date:
  116. version += time.strftime("-%Y%m%d")
  117. return version
  118. vtags = property(tags)
  119. class egg_info(InfoCommon, Command):
  120. description = "create a distribution's .egg-info directory"
  121. user_options = [
  122. ('egg-base=', 'e', "directory containing .egg-info directories"
  123. " (default: top of the source tree)"),
  124. ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
  125. ('tag-build=', 'b', "Specify explicit tag to add to version number"),
  126. ('no-date', 'D', "Don't include date stamp [default]"),
  127. ]
  128. boolean_options = ['tag-date']
  129. negative_opt = {
  130. 'no-date': 'tag-date',
  131. }
  132. def initialize_options(self):
  133. self.egg_base = None
  134. self.egg_name = None
  135. self.egg_info = None
  136. self.egg_version = None
  137. self.broken_egg_info = False
  138. ####################################
  139. # allow the 'tag_svn_revision' to be detected and
  140. # set, supporting sdists built on older Setuptools.
  141. @property
  142. def tag_svn_revision(self):
  143. pass
  144. @tag_svn_revision.setter
  145. def tag_svn_revision(self, value):
  146. pass
  147. ####################################
  148. def save_version_info(self, filename):
  149. """
  150. Materialize the value of date into the
  151. build tag. Install build keys in a deterministic order
  152. to avoid arbitrary reordering on subsequent builds.
  153. """
  154. egg_info = collections.OrderedDict()
  155. # follow the order these keys would have been added
  156. # when PYTHONHASHSEED=0
  157. egg_info['tag_build'] = self.tags()
  158. egg_info['tag_date'] = 0
  159. edit_config(filename, dict(egg_info=egg_info))
  160. def finalize_options(self):
  161. # Note: we need to capture the current value returned
  162. # by `self.tagged_version()`, so we can later update
  163. # `self.distribution.metadata.version` without
  164. # repercussions.
  165. self.egg_name = self.name
  166. self.egg_version = self.tagged_version()
  167. parsed_version = parse_version(self.egg_version)
  168. try:
  169. is_version = isinstance(parsed_version, packaging.version.Version)
  170. spec = (
  171. "%s==%s" if is_version else "%s===%s"
  172. )
  173. list(
  174. parse_requirements(spec % (self.egg_name, self.egg_version))
  175. )
  176. except ValueError:
  177. raise distutils.errors.DistutilsOptionError(
  178. "Invalid distribution name or version syntax: %s-%s" %
  179. (self.egg_name, self.egg_version)
  180. )
  181. if self.egg_base is None:
  182. dirs = self.distribution.package_dir
  183. self.egg_base = (dirs or {}).get('', os.curdir)
  184. self.ensure_dirname('egg_base')
  185. self.egg_info = to_filename(self.egg_name) + '.egg-info'
  186. if self.egg_base != os.curdir:
  187. self.egg_info = os.path.join(self.egg_base, self.egg_info)
  188. if '-' in self.egg_name:
  189. self.check_broken_egg_info()
  190. # Set package version for the benefit of dumber commands
  191. # (e.g. sdist, bdist_wininst, etc.)
  192. #
  193. self.distribution.metadata.version = self.egg_version
  194. # If we bootstrapped around the lack of a PKG-INFO, as might be the
  195. # case in a fresh checkout, make sure that any special tags get added
  196. # to the version info
  197. #
  198. pd = self.distribution._patched_dist
  199. if pd is not None and pd.key == self.egg_name.lower():
  200. pd._version = self.egg_version
  201. pd._parsed_version = parse_version(self.egg_version)
  202. self.distribution._patched_dist = None
  203. def write_or_delete_file(self, what, filename, data, force=False):
  204. """Write `data` to `filename` or delete if empty
  205. If `data` is non-empty, this routine is the same as ``write_file()``.
  206. If `data` is empty but not ``None``, this is the same as calling
  207. ``delete_file(filename)`. If `data` is ``None``, then this is a no-op
  208. unless `filename` exists, in which case a warning is issued about the
  209. orphaned file (if `force` is false), or deleted (if `force` is true).
  210. """
  211. if data:
  212. self.write_file(what, filename, data)
  213. elif os.path.exists(filename):
  214. if data is None and not force:
  215. log.warn(
  216. "%s not set in setup(), but %s exists", what, filename
  217. )
  218. return
  219. else:
  220. self.delete_file(filename)
  221. def write_file(self, what, filename, data):
  222. """Write `data` to `filename` (if not a dry run) after announcing it
  223. `what` is used in a log message to identify what is being written
  224. to the file.
  225. """
  226. log.info("writing %s to %s", what, filename)
  227. if six.PY3:
  228. data = data.encode("utf-8")
  229. if not self.dry_run:
  230. f = open(filename, 'wb')
  231. f.write(data)
  232. f.close()
  233. def delete_file(self, filename):
  234. """Delete `filename` (if not a dry run) after announcing it"""
  235. log.info("deleting %s", filename)
  236. if not self.dry_run:
  237. os.unlink(filename)
  238. def run(self):
  239. self.mkpath(self.egg_info)
  240. installer = self.distribution.fetch_build_egg
  241. for ep in iter_entry_points('egg_info.writers'):
  242. ep.require(installer=installer)
  243. writer = ep.resolve()
  244. writer(self, ep.name, os.path.join(self.egg_info, ep.name))
  245. # Get rid of native_libs.txt if it was put there by older bdist_egg
  246. nl = os.path.join(self.egg_info, "native_libs.txt")
  247. if os.path.exists(nl):
  248. self.delete_file(nl)
  249. self.find_sources()
  250. def find_sources(self):
  251. """Generate SOURCES.txt manifest file"""
  252. manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
  253. mm = manifest_maker(self.distribution)
  254. mm.manifest = manifest_filename
  255. mm.run()
  256. self.filelist = mm.filelist
  257. def check_broken_egg_info(self):
  258. bei = self.egg_name + '.egg-info'
  259. if self.egg_base != os.curdir:
  260. bei = os.path.join(self.egg_base, bei)
  261. if os.path.exists(bei):
  262. log.warn(
  263. "-" * 78 + '\n'
  264. "Note: Your current .egg-info directory has a '-' in its name;"
  265. '\nthis will not work correctly with "setup.py develop".\n\n'
  266. 'Please rename %s to %s to correct this problem.\n' + '-' * 78,
  267. bei, self.egg_info
  268. )
  269. self.broken_egg_info = self.egg_info
  270. self.egg_info = bei # make it work for now
  271. class FileList(_FileList):
  272. # Implementations of the various MANIFEST.in commands
  273. def process_template_line(self, line):
  274. # Parse the line: split it up, make sure the right number of words
  275. # is there, and return the relevant words. 'action' is always
  276. # defined: it's the first word of the line. Which of the other
  277. # three are defined depends on the action; it'll be either
  278. # patterns, (dir and patterns), or (dir_pattern).
  279. (action, patterns, dir, dir_pattern) = self._parse_template_line(line)
  280. # OK, now we know that the action is valid and we have the
  281. # right number of words on the line for that action -- so we
  282. # can proceed with minimal error-checking.
  283. if action == 'include':
  284. self.debug_print("include " + ' '.join(patterns))
  285. for pattern in patterns:
  286. if not self.include(pattern):
  287. log.warn("warning: no files found matching '%s'", pattern)
  288. elif action == 'exclude':
  289. self.debug_print("exclude " + ' '.join(patterns))
  290. for pattern in patterns:
  291. if not self.exclude(pattern):
  292. log.warn(("warning: no previously-included files "
  293. "found matching '%s'"), pattern)
  294. elif action == 'global-include':
  295. self.debug_print("global-include " + ' '.join(patterns))
  296. for pattern in patterns:
  297. if not self.global_include(pattern):
  298. log.warn(("warning: no files found matching '%s' "
  299. "anywhere in distribution"), pattern)
  300. elif action == 'global-exclude':
  301. self.debug_print("global-exclude " + ' '.join(patterns))
  302. for pattern in patterns:
  303. if not self.global_exclude(pattern):
  304. log.warn(("warning: no previously-included files matching "
  305. "'%s' found anywhere in distribution"),
  306. pattern)
  307. elif action == 'recursive-include':
  308. self.debug_print("recursive-include %s %s" %
  309. (dir, ' '.join(patterns)))
  310. for pattern in patterns:
  311. if not self.recursive_include(dir, pattern):
  312. log.warn(("warning: no files found matching '%s' "
  313. "under directory '%s'"),
  314. pattern, dir)
  315. elif action == 'recursive-exclude':
  316. self.debug_print("recursive-exclude %s %s" %
  317. (dir, ' '.join(patterns)))
  318. for pattern in patterns:
  319. if not self.recursive_exclude(dir, pattern):
  320. log.warn(("warning: no previously-included files matching "
  321. "'%s' found under directory '%s'"),
  322. pattern, dir)
  323. elif action == 'graft':
  324. self.debug_print("graft " + dir_pattern)
  325. if not self.graft(dir_pattern):
  326. log.warn("warning: no directories found matching '%s'",
  327. dir_pattern)
  328. elif action == 'prune':
  329. self.debug_print("prune " + dir_pattern)
  330. if not self.prune(dir_pattern):
  331. log.warn(("no previously-included directories found "
  332. "matching '%s'"), dir_pattern)
  333. else:
  334. raise DistutilsInternalError(
  335. "this cannot happen: invalid action '%s'" % action)
  336. def _remove_files(self, predicate):
  337. """
  338. Remove all files from the file list that match the predicate.
  339. Return True if any matching files were removed
  340. """
  341. found = False
  342. for i in range(len(self.files) - 1, -1, -1):
  343. if predicate(self.files[i]):
  344. self.debug_print(" removing " + self.files[i])
  345. del self.files[i]
  346. found = True
  347. return found
  348. def include(self, pattern):
  349. """Include files that match 'pattern'."""
  350. found = [f for f in glob(pattern) if not os.path.isdir(f)]
  351. self.extend(found)
  352. return bool(found)
  353. def exclude(self, pattern):
  354. """Exclude files that match 'pattern'."""
  355. match = translate_pattern(pattern)
  356. return self._remove_files(match.match)
  357. def recursive_include(self, dir, pattern):
  358. """
  359. Include all files anywhere in 'dir/' that match the pattern.
  360. """
  361. full_pattern = os.path.join(dir, '**', pattern)
  362. found = [f for f in glob(full_pattern, recursive=True)
  363. if not os.path.isdir(f)]
  364. self.extend(found)
  365. return bool(found)
  366. def recursive_exclude(self, dir, pattern):
  367. """
  368. Exclude any file anywhere in 'dir/' that match the pattern.
  369. """
  370. match = translate_pattern(os.path.join(dir, '**', pattern))
  371. return self._remove_files(match.match)
  372. def graft(self, dir):
  373. """Include all files from 'dir/'."""
  374. found = [
  375. item
  376. for match_dir in glob(dir)
  377. for item in distutils.filelist.findall(match_dir)
  378. ]
  379. self.extend(found)
  380. return bool(found)
  381. def prune(self, dir):
  382. """Filter out files from 'dir/'."""
  383. match = translate_pattern(os.path.join(dir, '**'))
  384. return self._remove_files(match.match)
  385. def global_include(self, pattern):
  386. """
  387. Include all files anywhere in the current directory that match the
  388. pattern. This is very inefficient on large file trees.
  389. """
  390. if self.allfiles is None:
  391. self.findall()
  392. match = translate_pattern(os.path.join('**', pattern))
  393. found = [f for f in self.allfiles if match.match(f)]
  394. self.extend(found)
  395. return bool(found)
  396. def global_exclude(self, pattern):
  397. """
  398. Exclude all files anywhere that match the pattern.
  399. """
  400. match = translate_pattern(os.path.join('**', pattern))
  401. return self._remove_files(match.match)
  402. def append(self, item):
  403. if item.endswith('\r'): # Fix older sdists built on Windows
  404. item = item[:-1]
  405. path = convert_path(item)
  406. if self._safe_path(path):
  407. self.files.append(path)
  408. def extend(self, paths):
  409. self.files.extend(filter(self._safe_path, paths))
  410. def _repair(self):
  411. """
  412. Replace self.files with only safe paths
  413. Because some owners of FileList manipulate the underlying
  414. ``files`` attribute directly, this method must be called to
  415. repair those paths.
  416. """
  417. self.files = list(filter(self._safe_path, self.files))
  418. def _safe_path(self, path):
  419. enc_warn = "'%s' not %s encodable -- skipping"
  420. # To avoid accidental trans-codings errors, first to unicode
  421. u_path = unicode_utils.filesys_decode(path)
  422. if u_path is None:
  423. log.warn("'%s' in unexpected encoding -- skipping" % path)
  424. return False
  425. # Must ensure utf-8 encodability
  426. utf8_path = unicode_utils.try_encode(u_path, "utf-8")
  427. if utf8_path is None:
  428. log.warn(enc_warn, path, 'utf-8')
  429. return False
  430. try:
  431. # accept is either way checks out
  432. if os.path.exists(u_path) or os.path.exists(utf8_path):
  433. return True
  434. # this will catch any encode errors decoding u_path
  435. except UnicodeEncodeError:
  436. log.warn(enc_warn, path, sys.getfilesystemencoding())
  437. class manifest_maker(sdist):
  438. template = "MANIFEST.in"
  439. def initialize_options(self):
  440. self.use_defaults = 1
  441. self.prune = 1
  442. self.manifest_only = 1
  443. self.force_manifest = 1
  444. def finalize_options(self):
  445. pass
  446. def run(self):
  447. self.filelist = FileList()
  448. if not os.path.exists(self.manifest):
  449. self.write_manifest() # it must exist so it'll get in the list
  450. self.add_defaults()
  451. if os.path.exists(self.template):
  452. self.read_template()
  453. self.prune_file_list()
  454. self.filelist.sort()
  455. self.filelist.remove_duplicates()
  456. self.write_manifest()
  457. def _manifest_normalize(self, path):
  458. path = unicode_utils.filesys_decode(path)
  459. return path.replace(os.sep, '/')
  460. def write_manifest(self):
  461. """
  462. Write the file list in 'self.filelist' to the manifest file
  463. named by 'self.manifest'.
  464. """
  465. self.filelist._repair()
  466. # Now _repairs should encodability, but not unicode
  467. files = [self._manifest_normalize(f) for f in self.filelist.files]
  468. msg = "writing manifest file '%s'" % self.manifest
  469. self.execute(write_file, (self.manifest, files), msg)
  470. def warn(self, msg):
  471. if not self._should_suppress_warning(msg):
  472. sdist.warn(self, msg)
  473. @staticmethod
  474. def _should_suppress_warning(msg):
  475. """
  476. suppress missing-file warnings from sdist
  477. """
  478. return re.match(r"standard file .*not found", msg)
  479. def add_defaults(self):
  480. sdist.add_defaults(self)
  481. self.filelist.append(self.template)
  482. self.filelist.append(self.manifest)
  483. rcfiles = list(walk_revctrl())
  484. if rcfiles:
  485. self.filelist.extend(rcfiles)
  486. elif os.path.exists(self.manifest):
  487. self.read_manifest()
  488. ei_cmd = self.get_finalized_command('egg_info')
  489. self.filelist.graft(ei_cmd.egg_info)
  490. def prune_file_list(self):
  491. build = self.get_finalized_command('build')
  492. base_dir = self.distribution.get_fullname()
  493. self.filelist.prune(build.build_base)
  494. self.filelist.prune(base_dir)
  495. sep = re.escape(os.sep)
  496. self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,
  497. is_regex=1)
  498. def write_file(filename, contents):
  499. """Create a file with the specified name and write 'contents' (a
  500. sequence of strings without line terminators) to it.
  501. """
  502. contents = "\n".join(contents)
  503. # assuming the contents has been vetted for utf-8 encoding
  504. contents = contents.encode("utf-8")
  505. with open(filename, "wb") as f: # always write POSIX-style manifest
  506. f.write(contents)
  507. def write_pkg_info(cmd, basename, filename):
  508. log.info("writing %s", filename)
  509. if not cmd.dry_run:
  510. metadata = cmd.distribution.metadata
  511. metadata.version, oldver = cmd.egg_version, metadata.version
  512. metadata.name, oldname = cmd.egg_name, metadata.name
  513. try:
  514. # write unescaped data to PKG-INFO, so older pkg_resources
  515. # can still parse it
  516. metadata.write_pkg_info(cmd.egg_info)
  517. finally:
  518. metadata.name, metadata.version = oldname, oldver
  519. safe = getattr(cmd.distribution, 'zip_safe', None)
  520. bdist_egg.write_safety_flag(cmd.egg_info, safe)
  521. def warn_depends_obsolete(cmd, basename, filename):
  522. if os.path.exists(filename):
  523. log.warn(
  524. "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
  525. "Use the install_requires/extras_require setup() args instead."
  526. )
  527. def _write_requirements(stream, reqs):
  528. lines = yield_lines(reqs or ())
  529. append_cr = lambda line: line + '\n'
  530. lines = map(append_cr, lines)
  531. stream.writelines(lines)
  532. def write_requirements(cmd, basename, filename):
  533. dist = cmd.distribution
  534. data = six.StringIO()
  535. _write_requirements(data, dist.install_requires)
  536. extras_require = dist.extras_require or {}
  537. for extra in sorted(extras_require):
  538. data.write('\n[{extra}]\n'.format(**vars()))
  539. _write_requirements(data, extras_require[extra])
  540. cmd.write_or_delete_file("requirements", filename, data.getvalue())
  541. def write_setup_requirements(cmd, basename, filename):
  542. data = io.StringIO()
  543. _write_requirements(data, cmd.distribution.setup_requires)
  544. cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())
  545. def write_toplevel_names(cmd, basename, filename):
  546. pkgs = dict.fromkeys(
  547. [
  548. k.split('.', 1)[0]
  549. for k in cmd.distribution.iter_distribution_names()
  550. ]
  551. )
  552. cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
  553. def overwrite_arg(cmd, basename, filename):
  554. write_arg(cmd, basename, filename, True)
  555. def write_arg(cmd, basename, filename, force=False):
  556. argname = os.path.splitext(basename)[0]
  557. value = getattr(cmd.distribution, argname, None)
  558. if value is not None:
  559. value = '\n'.join(value) + '\n'
  560. cmd.write_or_delete_file(argname, filename, value, force)
  561. def write_entries(cmd, basename, filename):
  562. ep = cmd.distribution.entry_points
  563. if isinstance(ep, six.string_types) or ep is None:
  564. data = ep
  565. elif ep is not None:
  566. data = []
  567. for section, contents in sorted(ep.items()):
  568. if not isinstance(contents, six.string_types):
  569. contents = EntryPoint.parse_group(section, contents)
  570. contents = '\n'.join(sorted(map(str, contents.values())))
  571. data.append('[%s]\n%s\n\n' % (section, contents))
  572. data = ''.join(data)
  573. cmd.write_or_delete_file('entry points', filename, data, True)
  574. def get_pkg_info_revision():
  575. """
  576. Get a -r### off of PKG-INFO Version in case this is an sdist of
  577. a subversion revision.
  578. """
  579. warnings.warn("get_pkg_info_revision is deprecated.", DeprecationWarning)
  580. if os.path.exists('PKG-INFO'):
  581. with io.open('PKG-INFO') as f:
  582. for line in f:
  583. match = re.match(r"Version:.*-r(\d+)\s*$", line)
  584. if match:
  585. return int(match.group(1))
  586. return 0