1 """setuptools.command.egg_info
3 Create a distribution's .egg-info directory and contents"""
5 # This module should be kept compatible with Python 2.3
7 from setuptools import Command
8 from distutils.errors import *
9 from distutils import log
10 from setuptools.command.sdist import sdist
11 from distutils.util import convert_path
12 from distutils.filelist import FileList
13 from pkg_resources import parse_requirements, safe_name, parse_version, \
14 safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename
15 from sdist import walk_revctrl
17 class egg_info(Command):
18 description = "create a distribution's .egg-info directory"
21 ('egg-base=', 'e', "directory containing .egg-info directories"
22 " (default: top of the source tree)"),
23 ('tag-svn-revision', 'r',
24 "Add subversion revision ID to version number"),
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-svn-revision', 'R',
28 "Don't add subversion revision ID [default]"),
29 ('no-date', 'D', "Don't include date stamp [default]"),
32 boolean_options = ['tag-date', 'tag-svn-revision']
33 negative_opt = {'no-svn-revision': 'tag-svn-revision',
34 'no-date': 'tag-date'}
42 def initialize_options(self):
44 self.egg_version = None
48 self.tag_svn_revision = 0
50 self.broken_egg_info = False
53 def save_version_info(self, filename):
54 from setopt import edit_config
58 {'tag_svn_revision':0, 'tag_date': 0, 'tag_build': self.tags()}
83 def finalize_options (self):
84 self.egg_name = safe_name(self.distribution.get_name())
85 self.vtags = self.tags()
86 self.egg_version = self.tagged_version()
90 parse_requirements('%s==%s' % (self.egg_name,self.egg_version))
93 raise DistutilsOptionError(
94 "Invalid distribution name or version syntax: %s-%s" %
95 (self.egg_name,self.egg_version)
98 if self.egg_base is None:
99 dirs = self.distribution.package_dir
100 self.egg_base = (dirs or {}).get('',os.curdir)
102 self.ensure_dirname('egg_base')
103 self.egg_info = to_filename(self.egg_name)+'.egg-info'
104 if self.egg_base != os.curdir:
105 self.egg_info = os.path.join(self.egg_base, self.egg_info)
106 if '-' in self.egg_name: self.check_broken_egg_info()
108 # Set package version for the benefit of dumber commands
109 # (e.g. sdist, bdist_wininst, etc.)
111 self.distribution.metadata.version = self.egg_version
113 # If we bootstrapped around the lack of a PKG-INFO, as might be the
114 # case in a fresh checkout, make sure that any special tags get added
115 # to the version info
117 pd = self.distribution._patched_dist
118 if pd is not None and pd.key==self.egg_name.lower():
119 pd._version = self.egg_version
120 pd._parsed_version = parse_version(self.egg_version)
121 self.distribution._patched_dist = None
124 def write_or_delete_file(self, what, filename, data, force=False):
125 """Write `data` to `filename` or delete if empty
127 If `data` is non-empty, this routine is the same as ``write_file()``.
128 If `data` is empty but not ``None``, this is the same as calling
129 ``delete_file(filename)`. If `data` is ``None``, then this is a no-op
130 unless `filename` exists, in which case a warning is issued about the
131 orphaned file (if `force` is false), or deleted (if `force` is true).
134 self.write_file(what, filename, data)
135 elif os.path.exists(filename):
136 if data is None and not force:
138 "%s not set in setup(), but %s exists", what, filename
142 self.delete_file(filename)
144 def write_file(self, what, filename, data):
145 """Write `data` to `filename` (if not a dry run) after announcing it
147 `what` is used in a log message to identify what is being written
150 log.info("writing %s to %s", what, filename)
152 f = open(filename, 'wb')
156 def delete_file(self, filename):
157 """Delete `filename` (if not a dry run) after announcing it"""
158 log.info("deleting %s", filename)
162 def tagged_version(self):
163 return safe_version(self.distribution.get_version() + self.vtags)
166 self.mkpath(self.egg_info)
167 installer = self.distribution.fetch_build_egg
168 for ep in iter_entry_points('egg_info.writers'):
169 writer = ep.load(installer=installer)
170 writer(self, ep.name, os.path.join(self.egg_info,ep.name))
172 # Get rid of native_libs.txt if it was put there by older bdist_egg
173 nl = os.path.join(self.egg_info, "native_libs.txt")
174 if os.path.exists(nl):
182 version+=self.tag_build
183 if self.tag_svn_revision and (
184 os.path.exists('.svn') or os.path.exists('PKG-INFO')
185 ): version += '-r%s' % self.get_svn_revision()
187 import time; version += time.strftime("-%Y%m%d")
206 def get_svn_revision(self):
208 urlre = re.compile('url="([^"]+)"')
209 revre = re.compile('committed-rev="(\d+)"')
211 for base,dirs,files in os.walk(os.curdir):
212 if '.svn' not in dirs:
214 continue # no sense walking uncontrolled subdirs
216 f = open(os.path.join(base,'.svn','entries'))
220 if data.startswith('<?xml'):
221 dirurl = urlre.search(data).group(1) # get repository URL
222 localrev = max([int(m.group(1)) for m in revre.finditer(data)]+[0])
224 try: svnver = int(data.splitlines()[0])
227 log.warn("unrecognized .svn/entries format; skipping %s", base)
231 data = map(str.splitlines,data.split('\n\x0c\n'))
232 del data[0][0] # get rid of the '8' or '9'
234 localrev = max([int(d[9]) for d in data if len(d)>9 and d[9]]+[0])
236 base_url = dirurl+'/' # save the root url
237 elif not dirurl.startswith(base_url):
239 continue # not part of the same svn tree, skip it
240 revision = max(revision, localrev)
242 return str(revision or get_pkg_info_revision())
247 def find_sources(self):
248 """Generate SOURCES.txt manifest file"""
249 manifest_filename = os.path.join(self.egg_info,"SOURCES.txt")
250 mm = manifest_maker(self.distribution)
251 mm.manifest = manifest_filename
253 self.filelist = mm.filelist
255 def check_broken_egg_info(self):
256 bei = self.egg_name+'.egg-info'
257 if self.egg_base != os.curdir:
258 bei = os.path.join(self.egg_base, bei)
259 if os.path.exists(bei):
262 "Note: Your current .egg-info directory has a '-' in its name;"
263 '\nthis will not work correctly with "setup.py develop".\n\n'
264 'Please rename %s to %s to correct this problem.\n'+'-'*78,
267 self.broken_egg_info = self.egg_info
268 self.egg_info = bei # make it work for now
270 class FileList(FileList):
271 """File list that accepts only existing, platform-independent paths"""
273 def append(self, item):
274 if item.endswith('\r'): # Fix older sdists built on Windows
276 path = convert_path(item)
277 if os.path.exists(path):
278 self.files.append(path)
288 class manifest_maker(sdist):
290 template = "MANIFEST.in"
292 def initialize_options (self):
293 self.use_defaults = 1
295 self.manifest_only = 1
296 self.force_manifest = 1
298 def finalize_options(self):
302 self.filelist = FileList()
303 if not os.path.exists(self.manifest):
304 self.write_manifest() # it must exist so it'll get in the list
305 self.filelist.findall()
307 if os.path.exists(self.template):
309 self.prune_file_list()
311 self.filelist.remove_duplicates()
312 self.write_manifest()
314 def write_manifest (self):
315 """Write the file list in 'self.filelist' (presumably as filled in
316 by 'add_defaults()' and 'read_template()') to the manifest file
317 named by 'self.manifest'.
319 files = self.filelist.files
321 files = [f.replace(os.sep,'/') for f in files]
322 self.execute(write_file, (self.manifest, files),
323 "writing manifest file '%s'" % self.manifest)
325 def warn(self, msg): # suppress missing-file warnings from sdist
326 if not msg.startswith("standard file not found:"):
327 sdist.warn(self, msg)
329 def add_defaults(self):
330 sdist.add_defaults(self)
331 self.filelist.append(self.template)
332 self.filelist.append(self.manifest)
333 rcfiles = list(walk_revctrl())
335 self.filelist.extend(rcfiles)
336 elif os.path.exists(self.manifest):
338 ei_cmd = self.get_finalized_command('egg_info')
339 self.filelist.include_pattern("*", prefix=ei_cmd.egg_info)
341 def prune_file_list (self):
342 build = self.get_finalized_command('build')
343 base_dir = self.distribution.get_fullname()
344 self.filelist.exclude_pattern(None, prefix=build.build_base)
345 self.filelist.exclude_pattern(None, prefix=base_dir)
346 sep = re.escape(os.sep)
347 self.filelist.exclude_pattern(sep+r'(RCS|CVS|\.svn)'+sep, is_regex=1)
350 def write_file (filename, contents):
351 """Create a file with the specified name and write 'contents' (a
352 sequence of strings without line terminators) to it.
354 f = open(filename, "wb") # always write POSIX-style manifest
355 f.write("\n".join(contents))
370 def write_pkg_info(cmd, basename, filename):
371 log.info("writing %s", filename)
373 metadata = cmd.distribution.metadata
374 metadata.version, oldver = cmd.egg_version, metadata.version
375 metadata.name, oldname = cmd.egg_name, metadata.name
377 # write unescaped data to PKG-INFO, so older pkg_resources
379 metadata.write_pkg_info(cmd.egg_info)
381 metadata.name, metadata.version = oldname, oldver
383 safe = getattr(cmd.distribution,'zip_safe',None)
384 import bdist_egg; bdist_egg.write_safety_flag(cmd.egg_info, safe)
386 def warn_depends_obsolete(cmd, basename, filename):
387 if os.path.exists(filename):
389 "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
390 "Use the install_requires/extras_require setup() args instead."
394 def write_requirements(cmd, basename, filename):
395 dist = cmd.distribution
396 data = ['\n'.join(yield_lines(dist.install_requires or ()))]
397 for extra,reqs in (dist.extras_require or {}).items():
398 data.append('\n\n[%s]\n%s' % (extra, '\n'.join(yield_lines(reqs))))
399 cmd.write_or_delete_file("requirements", filename, ''.join(data))
401 def write_toplevel_names(cmd, basename, filename):
402 pkgs = dict.fromkeys(
404 for k in cmd.distribution.iter_distribution_names()
407 cmd.write_file("top-level names", filename, '\n'.join(pkgs)+'\n')
411 def overwrite_arg(cmd, basename, filename):
412 write_arg(cmd, basename, filename, True)
414 def write_arg(cmd, basename, filename, force=False):
415 argname = os.path.splitext(basename)[0]
416 value = getattr(cmd.distribution, argname, None)
417 if value is not None:
418 value = '\n'.join(value)+'\n'
419 cmd.write_or_delete_file(argname, filename, value, force)
421 def write_entries(cmd, basename, filename):
422 ep = cmd.distribution.entry_points
424 if isinstance(ep,basestring) or ep is None:
428 for section, contents in ep.items():
429 if not isinstance(contents,basestring):
430 contents = EntryPoint.parse_group(section, contents)
431 contents = '\n'.join(map(str,contents.values()))
432 data.append('[%s]\n%s\n\n' % (section,contents))
435 cmd.write_or_delete_file('entry points', filename, data, True)
437 def get_pkg_info_revision():
438 # See if we can get a -r### off of PKG-INFO, in case this is an sdist of
439 # a subversion revision
441 if os.path.exists('PKG-INFO'):
442 f = open('PKG-INFO','rU')
444 match = re.match(r"Version:.*-r(\d+)\s*$", line)
446 return int(match.group(1))