egg_info.py 14 KB

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