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.
 
 
 
 

481 lines
16 KiB

  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.util import convert_path
  5. from distutils import log
  6. import distutils.errors
  7. import distutils.filelist
  8. import os
  9. import re
  10. import sys
  11. try:
  12. from setuptools_svn import svn_utils
  13. except ImportError:
  14. pass
  15. from setuptools import Command
  16. from setuptools.command.sdist import sdist
  17. from setuptools.compat import basestring, PY3, StringIO
  18. from setuptools.command.sdist import walk_revctrl
  19. from pkg_resources import (
  20. parse_requirements, safe_name, parse_version,
  21. safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
  22. import setuptools.unicode_utils as unicode_utils
  23. from pkg_resources import packaging
  24. class egg_info(Command):
  25. description = "create a distribution's .egg-info directory"
  26. user_options = [
  27. ('egg-base=', 'e', "directory containing .egg-info directories"
  28. " (default: top of the source tree)"),
  29. ('tag-svn-revision', 'r',
  30. "Add subversion revision ID to version number"),
  31. ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
  32. ('tag-build=', 'b', "Specify explicit tag to add to version number"),
  33. ('no-svn-revision', 'R',
  34. "Don't add subversion revision ID [default]"),
  35. ('no-date', 'D', "Don't include date stamp [default]"),
  36. ]
  37. boolean_options = ['tag-date', 'tag-svn-revision']
  38. negative_opt = {'no-svn-revision': 'tag-svn-revision',
  39. 'no-date': 'tag-date'}
  40. def initialize_options(self):
  41. self.egg_name = None
  42. self.egg_version = None
  43. self.egg_base = None
  44. self.egg_info = None
  45. self.tag_build = None
  46. self.tag_svn_revision = 0
  47. self.tag_date = 0
  48. self.broken_egg_info = False
  49. self.vtags = None
  50. def save_version_info(self, filename):
  51. from setuptools.command.setopt import edit_config
  52. values = dict(
  53. egg_info=dict(
  54. tag_svn_revision=0,
  55. tag_date=0,
  56. tag_build=self.tags(),
  57. )
  58. )
  59. edit_config(filename, values)
  60. def finalize_options(self):
  61. self.egg_name = safe_name(self.distribution.get_name())
  62. self.vtags = self.tags()
  63. self.egg_version = self.tagged_version()
  64. parsed_version = parse_version(self.egg_version)
  65. try:
  66. is_version = isinstance(parsed_version, packaging.version.Version)
  67. spec = (
  68. "%s==%s" if is_version else "%s===%s"
  69. )
  70. list(
  71. parse_requirements(spec % (self.egg_name, self.egg_version))
  72. )
  73. except ValueError:
  74. raise distutils.errors.DistutilsOptionError(
  75. "Invalid distribution name or version syntax: %s-%s" %
  76. (self.egg_name, self.egg_version)
  77. )
  78. if self.egg_base is None:
  79. dirs = self.distribution.package_dir
  80. self.egg_base = (dirs or {}).get('', os.curdir)
  81. self.ensure_dirname('egg_base')
  82. self.egg_info = to_filename(self.egg_name) + '.egg-info'
  83. if self.egg_base != os.curdir:
  84. self.egg_info = os.path.join(self.egg_base, self.egg_info)
  85. if '-' in self.egg_name:
  86. self.check_broken_egg_info()
  87. # Set package version for the benefit of dumber commands
  88. # (e.g. sdist, bdist_wininst, etc.)
  89. #
  90. self.distribution.metadata.version = self.egg_version
  91. # If we bootstrapped around the lack of a PKG-INFO, as might be the
  92. # case in a fresh checkout, make sure that any special tags get added
  93. # to the version info
  94. #
  95. pd = self.distribution._patched_dist
  96. if pd is not None and pd.key == self.egg_name.lower():
  97. pd._version = self.egg_version
  98. pd._parsed_version = parse_version(self.egg_version)
  99. self.distribution._patched_dist = None
  100. def write_or_delete_file(self, what, filename, data, force=False):
  101. """Write `data` to `filename` or delete if empty
  102. If `data` is non-empty, this routine is the same as ``write_file()``.
  103. If `data` is empty but not ``None``, this is the same as calling
  104. ``delete_file(filename)`. If `data` is ``None``, then this is a no-op
  105. unless `filename` exists, in which case a warning is issued about the
  106. orphaned file (if `force` is false), or deleted (if `force` is true).
  107. """
  108. if data:
  109. self.write_file(what, filename, data)
  110. elif os.path.exists(filename):
  111. if data is None and not force:
  112. log.warn(
  113. "%s not set in setup(), but %s exists", what, filename
  114. )
  115. return
  116. else:
  117. self.delete_file(filename)
  118. def write_file(self, what, filename, data):
  119. """Write `data` to `filename` (if not a dry run) after announcing it
  120. `what` is used in a log message to identify what is being written
  121. to the file.
  122. """
  123. log.info("writing %s to %s", what, filename)
  124. if PY3:
  125. data = data.encode("utf-8")
  126. if not self.dry_run:
  127. f = open(filename, 'wb')
  128. f.write(data)
  129. f.close()
  130. def delete_file(self, filename):
  131. """Delete `filename` (if not a dry run) after announcing it"""
  132. log.info("deleting %s", filename)
  133. if not self.dry_run:
  134. os.unlink(filename)
  135. def tagged_version(self):
  136. version = self.distribution.get_version()
  137. # egg_info may be called more than once for a distribution,
  138. # in which case the version string already contains all tags.
  139. if self.vtags and version.endswith(self.vtags):
  140. return safe_version(version)
  141. return safe_version(version + self.vtags)
  142. def run(self):
  143. self.mkpath(self.egg_info)
  144. installer = self.distribution.fetch_build_egg
  145. for ep in iter_entry_points('egg_info.writers'):
  146. ep.require(installer=installer)
  147. writer = ep.resolve()
  148. writer(self, ep.name, os.path.join(self.egg_info, ep.name))
  149. # Get rid of native_libs.txt if it was put there by older bdist_egg
  150. nl = os.path.join(self.egg_info, "native_libs.txt")
  151. if os.path.exists(nl):
  152. self.delete_file(nl)
  153. self.find_sources()
  154. def tags(self):
  155. version = ''
  156. if self.tag_build:
  157. version += self.tag_build
  158. if self.tag_svn_revision:
  159. rev = self.get_svn_revision()
  160. if rev: # is 0 if it's not an svn working copy
  161. version += '-r%s' % rev
  162. if self.tag_date:
  163. import time
  164. version += time.strftime("-%Y%m%d")
  165. return version
  166. @staticmethod
  167. def get_svn_revision():
  168. if 'svn_utils' not in globals():
  169. return "0"
  170. return str(svn_utils.SvnInfo.load(os.curdir).get_revision())
  171. def find_sources(self):
  172. """Generate SOURCES.txt manifest file"""
  173. manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
  174. mm = manifest_maker(self.distribution)
  175. mm.manifest = manifest_filename
  176. mm.run()
  177. self.filelist = mm.filelist
  178. def check_broken_egg_info(self):
  179. bei = self.egg_name + '.egg-info'
  180. if self.egg_base != os.curdir:
  181. bei = os.path.join(self.egg_base, bei)
  182. if os.path.exists(bei):
  183. log.warn(
  184. "-" * 78 + '\n'
  185. "Note: Your current .egg-info directory has a '-' in its name;"
  186. '\nthis will not work correctly with "setup.py develop".\n\n'
  187. 'Please rename %s to %s to correct this problem.\n' + '-' * 78,
  188. bei, self.egg_info
  189. )
  190. self.broken_egg_info = self.egg_info
  191. self.egg_info = bei # make it work for now
  192. class FileList(_FileList):
  193. """File list that accepts only existing, platform-independent paths"""
  194. def append(self, item):
  195. if item.endswith('\r'): # Fix older sdists built on Windows
  196. item = item[:-1]
  197. path = convert_path(item)
  198. if self._safe_path(path):
  199. self.files.append(path)
  200. def extend(self, paths):
  201. self.files.extend(filter(self._safe_path, paths))
  202. def _repair(self):
  203. """
  204. Replace self.files with only safe paths
  205. Because some owners of FileList manipulate the underlying
  206. ``files`` attribute directly, this method must be called to
  207. repair those paths.
  208. """
  209. self.files = list(filter(self._safe_path, self.files))
  210. def _safe_path(self, path):
  211. enc_warn = "'%s' not %s encodable -- skipping"
  212. # To avoid accidental trans-codings errors, first to unicode
  213. u_path = unicode_utils.filesys_decode(path)
  214. if u_path is None:
  215. log.warn("'%s' in unexpected encoding -- skipping" % path)
  216. return False
  217. # Must ensure utf-8 encodability
  218. utf8_path = unicode_utils.try_encode(u_path, "utf-8")
  219. if utf8_path is None:
  220. log.warn(enc_warn, path, 'utf-8')
  221. return False
  222. try:
  223. # accept is either way checks out
  224. if os.path.exists(u_path) or os.path.exists(utf8_path):
  225. return True
  226. # this will catch any encode errors decoding u_path
  227. except UnicodeEncodeError:
  228. log.warn(enc_warn, path, sys.getfilesystemencoding())
  229. class manifest_maker(sdist):
  230. template = "MANIFEST.in"
  231. def initialize_options(self):
  232. self.use_defaults = 1
  233. self.prune = 1
  234. self.manifest_only = 1
  235. self.force_manifest = 1
  236. def finalize_options(self):
  237. pass
  238. def run(self):
  239. self.filelist = FileList()
  240. if not os.path.exists(self.manifest):
  241. self.write_manifest() # it must exist so it'll get in the list
  242. self.filelist.findall()
  243. self.add_defaults()
  244. if os.path.exists(self.template):
  245. self.read_template()
  246. self.prune_file_list()
  247. self.filelist.sort()
  248. self.filelist.remove_duplicates()
  249. self.write_manifest()
  250. def _manifest_normalize(self, path):
  251. path = unicode_utils.filesys_decode(path)
  252. return path.replace(os.sep, '/')
  253. def write_manifest(self):
  254. """
  255. Write the file list in 'self.filelist' to the manifest file
  256. named by 'self.manifest'.
  257. """
  258. self.filelist._repair()
  259. # Now _repairs should encodability, but not unicode
  260. files = [self._manifest_normalize(f) for f in self.filelist.files]
  261. msg = "writing manifest file '%s'" % self.manifest
  262. self.execute(write_file, (self.manifest, files), msg)
  263. def warn(self, msg): # suppress missing-file warnings from sdist
  264. if not msg.startswith("standard file not found:"):
  265. sdist.warn(self, msg)
  266. def add_defaults(self):
  267. sdist.add_defaults(self)
  268. self.filelist.append(self.template)
  269. self.filelist.append(self.manifest)
  270. rcfiles = list(walk_revctrl())
  271. if rcfiles:
  272. self.filelist.extend(rcfiles)
  273. elif os.path.exists(self.manifest):
  274. self.read_manifest()
  275. ei_cmd = self.get_finalized_command('egg_info')
  276. self._add_egg_info(cmd=ei_cmd)
  277. self.filelist.include_pattern("*", prefix=ei_cmd.egg_info)
  278. def _add_egg_info(self, cmd):
  279. """
  280. Add paths for egg-info files for an external egg-base.
  281. The egg-info files are written to egg-base. If egg-base is
  282. outside the current working directory, this method
  283. searchs the egg-base directory for files to include
  284. in the manifest. Uses distutils.filelist.findall (which is
  285. really the version monkeypatched in by setuptools/__init__.py)
  286. to perform the search.
  287. Since findall records relative paths, prefix the returned
  288. paths with cmd.egg_base, so add_default's include_pattern call
  289. (which is looking for the absolute cmd.egg_info) will match
  290. them.
  291. """
  292. if cmd.egg_base == os.curdir:
  293. # egg-info files were already added by something else
  294. return
  295. discovered = distutils.filelist.findall(cmd.egg_base)
  296. resolved = (os.path.join(cmd.egg_base, path) for path in discovered)
  297. self.filelist.allfiles.extend(resolved)
  298. def prune_file_list(self):
  299. build = self.get_finalized_command('build')
  300. base_dir = self.distribution.get_fullname()
  301. self.filelist.exclude_pattern(None, prefix=build.build_base)
  302. self.filelist.exclude_pattern(None, prefix=base_dir)
  303. sep = re.escape(os.sep)
  304. self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,
  305. is_regex=1)
  306. def write_file(filename, contents):
  307. """Create a file with the specified name and write 'contents' (a
  308. sequence of strings without line terminators) to it.
  309. """
  310. contents = "\n".join(contents)
  311. # assuming the contents has been vetted for utf-8 encoding
  312. contents = contents.encode("utf-8")
  313. with open(filename, "wb") as f: # always write POSIX-style manifest
  314. f.write(contents)
  315. def write_pkg_info(cmd, basename, filename):
  316. log.info("writing %s", filename)
  317. if not cmd.dry_run:
  318. metadata = cmd.distribution.metadata
  319. metadata.version, oldver = cmd.egg_version, metadata.version
  320. metadata.name, oldname = cmd.egg_name, metadata.name
  321. try:
  322. # write unescaped data to PKG-INFO, so older pkg_resources
  323. # can still parse it
  324. metadata.write_pkg_info(cmd.egg_info)
  325. finally:
  326. metadata.name, metadata.version = oldname, oldver
  327. safe = getattr(cmd.distribution, 'zip_safe', None)
  328. from setuptools.command import bdist_egg
  329. bdist_egg.write_safety_flag(cmd.egg_info, safe)
  330. def warn_depends_obsolete(cmd, basename, filename):
  331. if os.path.exists(filename):
  332. log.warn(
  333. "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
  334. "Use the install_requires/extras_require setup() args instead."
  335. )
  336. def _write_requirements(stream, reqs):
  337. lines = yield_lines(reqs or ())
  338. append_cr = lambda line: line + '\n'
  339. lines = map(append_cr, lines)
  340. stream.writelines(lines)
  341. def write_requirements(cmd, basename, filename):
  342. dist = cmd.distribution
  343. data = StringIO()
  344. _write_requirements(data, dist.install_requires)
  345. extras_require = dist.extras_require or {}
  346. for extra in sorted(extras_require):
  347. data.write('\n[{extra}]\n'.format(**vars()))
  348. _write_requirements(data, extras_require[extra])
  349. cmd.write_or_delete_file("requirements", filename, data.getvalue())
  350. def write_setup_requirements(cmd, basename, filename):
  351. data = StringIO()
  352. _write_requirements(data, cmd.distribution.setup_requires)
  353. cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())
  354. def write_toplevel_names(cmd, basename, filename):
  355. pkgs = dict.fromkeys(
  356. [
  357. k.split('.', 1)[0]
  358. for k in cmd.distribution.iter_distribution_names()
  359. ]
  360. )
  361. cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
  362. def overwrite_arg(cmd, basename, filename):
  363. write_arg(cmd, basename, filename, True)
  364. def write_arg(cmd, basename, filename, force=False):
  365. argname = os.path.splitext(basename)[0]
  366. value = getattr(cmd.distribution, argname, None)
  367. if value is not None:
  368. value = '\n'.join(value) + '\n'
  369. cmd.write_or_delete_file(argname, filename, value, force)
  370. def write_entries(cmd, basename, filename):
  371. ep = cmd.distribution.entry_points
  372. if isinstance(ep, basestring) or ep is None:
  373. data = ep
  374. elif ep is not None:
  375. data = []
  376. for section, contents in sorted(ep.items()):
  377. if not isinstance(contents, basestring):
  378. contents = EntryPoint.parse_group(section, contents)
  379. contents = '\n'.join(sorted(map(str, contents.values())))
  380. data.append('[%s]\n%s\n\n' % (section, contents))
  381. data = ''.join(data)
  382. cmd.write_or_delete_file('entry points', filename, data, True)
  383. def get_pkg_info_revision():
  384. # See if we can get a -r### off of PKG-INFO, in case this is an sdist of
  385. # a subversion revision
  386. #
  387. if os.path.exists('PKG-INFO'):
  388. f = open('PKG-INFO', 'rU')
  389. for line in f:
  390. match = re.match(r"Version:.*-r(\d+)\s*$", line)
  391. if match:
  392. return int(match.group(1))
  393. f.close()
  394. return 0