Path: blob/master/venv/Lib/site-packages/setuptools/command/egg_info.py
811 views
"""setuptools.command.egg_info12Create a distribution's .egg-info directory and contents"""34from distutils.filelist import FileList as _FileList5from distutils.errors import DistutilsInternalError6from distutils.util import convert_path7from distutils import log8import distutils.errors9import distutils.filelist10import os11import re12import sys13import io14import warnings15import time16import collections1718from setuptools.extern import six19from setuptools.extern.six.moves import map2021from setuptools import Command22from setuptools.command.sdist import sdist23from setuptools.command.sdist import walk_revctrl24from setuptools.command.setopt import edit_config25from setuptools.command import bdist_egg26from pkg_resources import (27parse_requirements, safe_name, parse_version,28safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)29import setuptools.unicode_utils as unicode_utils30from setuptools.glob import glob3132from setuptools.extern import packaging33from setuptools import SetuptoolsDeprecationWarning343536def translate_pattern(glob):37"""38Translate a file path glob like '*.txt' in to a regular expression.39This differs from fnmatch.translate which allows wildcards to match40directory separators. It also knows about '**/' which matches any number of41directories.42"""43pat = ''4445# This will split on '/' within [character classes]. This is deliberate.46chunks = glob.split(os.path.sep)4748sep = re.escape(os.sep)49valid_char = '[^%s]' % (sep,)5051for c, chunk in enumerate(chunks):52last_chunk = c == len(chunks) - 15354# Chunks that are a literal ** are globstars. They match anything.55if chunk == '**':56if last_chunk:57# Match anything if this is the last component58pat += '.*'59else:60# Match '(name/)*'61pat += '(?:%s+%s)*' % (valid_char, sep)62continue # Break here as the whole path component has been handled6364# Find any special characters in the remainder65i = 066chunk_len = len(chunk)67while i < chunk_len:68char = chunk[i]69if char == '*':70# Match any number of name characters71pat += valid_char + '*'72elif char == '?':73# Match a name character74pat += valid_char75elif char == '[':76# Character class77inner_i = i + 178# Skip initial !/] chars79if inner_i < chunk_len and chunk[inner_i] == '!':80inner_i = inner_i + 181if inner_i < chunk_len and chunk[inner_i] == ']':82inner_i = inner_i + 18384# Loop till the closing ] is found85while inner_i < chunk_len and chunk[inner_i] != ']':86inner_i = inner_i + 18788if inner_i >= chunk_len:89# Got to the end of the string without finding a closing ]90# Do not treat this as a matching group, but as a literal [91pat += re.escape(char)92else:93# Grab the insides of the [brackets]94inner = chunk[i + 1:inner_i]95char_class = ''9697# Class negation98if inner[0] == '!':99char_class = '^'100inner = inner[1:]101102char_class += re.escape(inner)103pat += '[%s]' % (char_class,)104105# Skip to the end ]106i = inner_i107else:108pat += re.escape(char)109i += 1110111# Join each chunk with the dir separator112if not last_chunk:113pat += sep114115pat += r'\Z'116return re.compile(pat, flags=re.MULTILINE | re.DOTALL)117118119class InfoCommon:120tag_build = None121tag_date = None122123@property124def name(self):125return safe_name(self.distribution.get_name())126127def tagged_version(self):128version = self.distribution.get_version()129# egg_info may be called more than once for a distribution,130# in which case the version string already contains all tags.131if self.vtags and version.endswith(self.vtags):132return safe_version(version)133return safe_version(version + self.vtags)134135def tags(self):136version = ''137if self.tag_build:138version += self.tag_build139if self.tag_date:140version += time.strftime("-%Y%m%d")141return version142vtags = property(tags)143144145class egg_info(InfoCommon, Command):146description = "create a distribution's .egg-info directory"147148user_options = [149('egg-base=', 'e', "directory containing .egg-info directories"150" (default: top of the source tree)"),151('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),152('tag-build=', 'b', "Specify explicit tag to add to version number"),153('no-date', 'D', "Don't include date stamp [default]"),154]155156boolean_options = ['tag-date']157negative_opt = {158'no-date': 'tag-date',159}160161def initialize_options(self):162self.egg_base = None163self.egg_name = None164self.egg_info = None165self.egg_version = None166self.broken_egg_info = False167168####################################169# allow the 'tag_svn_revision' to be detected and170# set, supporting sdists built on older Setuptools.171@property172def tag_svn_revision(self):173pass174175@tag_svn_revision.setter176def tag_svn_revision(self, value):177pass178####################################179180def save_version_info(self, filename):181"""182Materialize the value of date into the183build tag. Install build keys in a deterministic order184to avoid arbitrary reordering on subsequent builds.185"""186egg_info = collections.OrderedDict()187# follow the order these keys would have been added188# when PYTHONHASHSEED=0189egg_info['tag_build'] = self.tags()190egg_info['tag_date'] = 0191edit_config(filename, dict(egg_info=egg_info))192193def finalize_options(self):194# Note: we need to capture the current value returned195# by `self.tagged_version()`, so we can later update196# `self.distribution.metadata.version` without197# repercussions.198self.egg_name = self.name199self.egg_version = self.tagged_version()200parsed_version = parse_version(self.egg_version)201202try:203is_version = isinstance(parsed_version, packaging.version.Version)204spec = (205"%s==%s" if is_version else "%s===%s"206)207list(208parse_requirements(spec % (self.egg_name, self.egg_version))209)210except ValueError as e:211raise distutils.errors.DistutilsOptionError(212"Invalid distribution name or version syntax: %s-%s" %213(self.egg_name, self.egg_version)214) from e215216if self.egg_base is None:217dirs = self.distribution.package_dir218self.egg_base = (dirs or {}).get('', os.curdir)219220self.ensure_dirname('egg_base')221self.egg_info = to_filename(self.egg_name) + '.egg-info'222if self.egg_base != os.curdir:223self.egg_info = os.path.join(self.egg_base, self.egg_info)224if '-' in self.egg_name:225self.check_broken_egg_info()226227# Set package version for the benefit of dumber commands228# (e.g. sdist, bdist_wininst, etc.)229#230self.distribution.metadata.version = self.egg_version231232# If we bootstrapped around the lack of a PKG-INFO, as might be the233# case in a fresh checkout, make sure that any special tags get added234# to the version info235#236pd = self.distribution._patched_dist237if pd is not None and pd.key == self.egg_name.lower():238pd._version = self.egg_version239pd._parsed_version = parse_version(self.egg_version)240self.distribution._patched_dist = None241242def write_or_delete_file(self, what, filename, data, force=False):243"""Write `data` to `filename` or delete if empty244245If `data` is non-empty, this routine is the same as ``write_file()``.246If `data` is empty but not ``None``, this is the same as calling247``delete_file(filename)`. If `data` is ``None``, then this is a no-op248unless `filename` exists, in which case a warning is issued about the249orphaned file (if `force` is false), or deleted (if `force` is true).250"""251if data:252self.write_file(what, filename, data)253elif os.path.exists(filename):254if data is None and not force:255log.warn(256"%s not set in setup(), but %s exists", what, filename257)258return259else:260self.delete_file(filename)261262def write_file(self, what, filename, data):263"""Write `data` to `filename` (if not a dry run) after announcing it264265`what` is used in a log message to identify what is being written266to the file.267"""268log.info("writing %s to %s", what, filename)269if not six.PY2:270data = data.encode("utf-8")271if not self.dry_run:272f = open(filename, 'wb')273f.write(data)274f.close()275276def delete_file(self, filename):277"""Delete `filename` (if not a dry run) after announcing it"""278log.info("deleting %s", filename)279if not self.dry_run:280os.unlink(filename)281282def run(self):283self.mkpath(self.egg_info)284os.utime(self.egg_info, None)285installer = self.distribution.fetch_build_egg286for ep in iter_entry_points('egg_info.writers'):287ep.require(installer=installer)288writer = ep.resolve()289writer(self, ep.name, os.path.join(self.egg_info, ep.name))290291# Get rid of native_libs.txt if it was put there by older bdist_egg292nl = os.path.join(self.egg_info, "native_libs.txt")293if os.path.exists(nl):294self.delete_file(nl)295296self.find_sources()297298def find_sources(self):299"""Generate SOURCES.txt manifest file"""300manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")301mm = manifest_maker(self.distribution)302mm.manifest = manifest_filename303mm.run()304self.filelist = mm.filelist305306def check_broken_egg_info(self):307bei = self.egg_name + '.egg-info'308if self.egg_base != os.curdir:309bei = os.path.join(self.egg_base, bei)310if os.path.exists(bei):311log.warn(312"-" * 78 + '\n'313"Note: Your current .egg-info directory has a '-' in its name;"314'\nthis will not work correctly with "setup.py develop".\n\n'315'Please rename %s to %s to correct this problem.\n' + '-' * 78,316bei, self.egg_info317)318self.broken_egg_info = self.egg_info319self.egg_info = bei # make it work for now320321322class FileList(_FileList):323# Implementations of the various MANIFEST.in commands324325def process_template_line(self, line):326# Parse the line: split it up, make sure the right number of words327# is there, and return the relevant words. 'action' is always328# defined: it's the first word of the line. Which of the other329# three are defined depends on the action; it'll be either330# patterns, (dir and patterns), or (dir_pattern).331(action, patterns, dir, dir_pattern) = self._parse_template_line(line)332333# OK, now we know that the action is valid and we have the334# right number of words on the line for that action -- so we335# can proceed with minimal error-checking.336if action == 'include':337self.debug_print("include " + ' '.join(patterns))338for pattern in patterns:339if not self.include(pattern):340log.warn("warning: no files found matching '%s'", pattern)341342elif action == 'exclude':343self.debug_print("exclude " + ' '.join(patterns))344for pattern in patterns:345if not self.exclude(pattern):346log.warn(("warning: no previously-included files "347"found matching '%s'"), pattern)348349elif action == 'global-include':350self.debug_print("global-include " + ' '.join(patterns))351for pattern in patterns:352if not self.global_include(pattern):353log.warn(("warning: no files found matching '%s' "354"anywhere in distribution"), pattern)355356elif action == 'global-exclude':357self.debug_print("global-exclude " + ' '.join(patterns))358for pattern in patterns:359if not self.global_exclude(pattern):360log.warn(("warning: no previously-included files matching "361"'%s' found anywhere in distribution"),362pattern)363364elif action == 'recursive-include':365self.debug_print("recursive-include %s %s" %366(dir, ' '.join(patterns)))367for pattern in patterns:368if not self.recursive_include(dir, pattern):369log.warn(("warning: no files found matching '%s' "370"under directory '%s'"),371pattern, dir)372373elif action == 'recursive-exclude':374self.debug_print("recursive-exclude %s %s" %375(dir, ' '.join(patterns)))376for pattern in patterns:377if not self.recursive_exclude(dir, pattern):378log.warn(("warning: no previously-included files matching "379"'%s' found under directory '%s'"),380pattern, dir)381382elif action == 'graft':383self.debug_print("graft " + dir_pattern)384if not self.graft(dir_pattern):385log.warn("warning: no directories found matching '%s'",386dir_pattern)387388elif action == 'prune':389self.debug_print("prune " + dir_pattern)390if not self.prune(dir_pattern):391log.warn(("no previously-included directories found "392"matching '%s'"), dir_pattern)393394else:395raise DistutilsInternalError(396"this cannot happen: invalid action '%s'" % action)397398def _remove_files(self, predicate):399"""400Remove all files from the file list that match the predicate.401Return True if any matching files were removed402"""403found = False404for i in range(len(self.files) - 1, -1, -1):405if predicate(self.files[i]):406self.debug_print(" removing " + self.files[i])407del self.files[i]408found = True409return found410411def include(self, pattern):412"""Include files that match 'pattern'."""413found = [f for f in glob(pattern) if not os.path.isdir(f)]414self.extend(found)415return bool(found)416417def exclude(self, pattern):418"""Exclude files that match 'pattern'."""419match = translate_pattern(pattern)420return self._remove_files(match.match)421422def recursive_include(self, dir, pattern):423"""424Include all files anywhere in 'dir/' that match the pattern.425"""426full_pattern = os.path.join(dir, '**', pattern)427found = [f for f in glob(full_pattern, recursive=True)428if not os.path.isdir(f)]429self.extend(found)430return bool(found)431432def recursive_exclude(self, dir, pattern):433"""434Exclude any file anywhere in 'dir/' that match the pattern.435"""436match = translate_pattern(os.path.join(dir, '**', pattern))437return self._remove_files(match.match)438439def graft(self, dir):440"""Include all files from 'dir/'."""441found = [442item443for match_dir in glob(dir)444for item in distutils.filelist.findall(match_dir)445]446self.extend(found)447return bool(found)448449def prune(self, dir):450"""Filter out files from 'dir/'."""451match = translate_pattern(os.path.join(dir, '**'))452return self._remove_files(match.match)453454def global_include(self, pattern):455"""456Include all files anywhere in the current directory that match the457pattern. This is very inefficient on large file trees.458"""459if self.allfiles is None:460self.findall()461match = translate_pattern(os.path.join('**', pattern))462found = [f for f in self.allfiles if match.match(f)]463self.extend(found)464return bool(found)465466def global_exclude(self, pattern):467"""468Exclude all files anywhere that match the pattern.469"""470match = translate_pattern(os.path.join('**', pattern))471return self._remove_files(match.match)472473def append(self, item):474if item.endswith('\r'): # Fix older sdists built on Windows475item = item[:-1]476path = convert_path(item)477478if self._safe_path(path):479self.files.append(path)480481def extend(self, paths):482self.files.extend(filter(self._safe_path, paths))483484def _repair(self):485"""486Replace self.files with only safe paths487488Because some owners of FileList manipulate the underlying489``files`` attribute directly, this method must be called to490repair those paths.491"""492self.files = list(filter(self._safe_path, self.files))493494def _safe_path(self, path):495enc_warn = "'%s' not %s encodable -- skipping"496497# To avoid accidental trans-codings errors, first to unicode498u_path = unicode_utils.filesys_decode(path)499if u_path is None:500log.warn("'%s' in unexpected encoding -- skipping" % path)501return False502503# Must ensure utf-8 encodability504utf8_path = unicode_utils.try_encode(u_path, "utf-8")505if utf8_path is None:506log.warn(enc_warn, path, 'utf-8')507return False508509try:510# accept is either way checks out511if os.path.exists(u_path) or os.path.exists(utf8_path):512return True513# this will catch any encode errors decoding u_path514except UnicodeEncodeError:515log.warn(enc_warn, path, sys.getfilesystemencoding())516517518class manifest_maker(sdist):519template = "MANIFEST.in"520521def initialize_options(self):522self.use_defaults = 1523self.prune = 1524self.manifest_only = 1525self.force_manifest = 1526527def finalize_options(self):528pass529530def run(self):531self.filelist = FileList()532if not os.path.exists(self.manifest):533self.write_manifest() # it must exist so it'll get in the list534self.add_defaults()535if os.path.exists(self.template):536self.read_template()537self.prune_file_list()538self.filelist.sort()539self.filelist.remove_duplicates()540self.write_manifest()541542def _manifest_normalize(self, path):543path = unicode_utils.filesys_decode(path)544return path.replace(os.sep, '/')545546def write_manifest(self):547"""548Write the file list in 'self.filelist' to the manifest file549named by 'self.manifest'.550"""551self.filelist._repair()552553# Now _repairs should encodability, but not unicode554files = [self._manifest_normalize(f) for f in self.filelist.files]555msg = "writing manifest file '%s'" % self.manifest556self.execute(write_file, (self.manifest, files), msg)557558def warn(self, msg):559if not self._should_suppress_warning(msg):560sdist.warn(self, msg)561562@staticmethod563def _should_suppress_warning(msg):564"""565suppress missing-file warnings from sdist566"""567return re.match(r"standard file .*not found", msg)568569def add_defaults(self):570sdist.add_defaults(self)571self.check_license()572self.filelist.append(self.template)573self.filelist.append(self.manifest)574rcfiles = list(walk_revctrl())575if rcfiles:576self.filelist.extend(rcfiles)577elif os.path.exists(self.manifest):578self.read_manifest()579580if os.path.exists("setup.py"):581# setup.py should be included by default, even if it's not582# the script called to create the sdist583self.filelist.append("setup.py")584585ei_cmd = self.get_finalized_command('egg_info')586self.filelist.graft(ei_cmd.egg_info)587588def prune_file_list(self):589build = self.get_finalized_command('build')590base_dir = self.distribution.get_fullname()591self.filelist.prune(build.build_base)592self.filelist.prune(base_dir)593sep = re.escape(os.sep)594self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,595is_regex=1)596597598def write_file(filename, contents):599"""Create a file with the specified name and write 'contents' (a600sequence of strings without line terminators) to it.601"""602contents = "\n".join(contents)603604# assuming the contents has been vetted for utf-8 encoding605contents = contents.encode("utf-8")606607with open(filename, "wb") as f: # always write POSIX-style manifest608f.write(contents)609610611def write_pkg_info(cmd, basename, filename):612log.info("writing %s", filename)613if not cmd.dry_run:614metadata = cmd.distribution.metadata615metadata.version, oldver = cmd.egg_version, metadata.version616metadata.name, oldname = cmd.egg_name, metadata.name617618try:619# write unescaped data to PKG-INFO, so older pkg_resources620# can still parse it621metadata.write_pkg_info(cmd.egg_info)622finally:623metadata.name, metadata.version = oldname, oldver624625safe = getattr(cmd.distribution, 'zip_safe', None)626627bdist_egg.write_safety_flag(cmd.egg_info, safe)628629630def warn_depends_obsolete(cmd, basename, filename):631if os.path.exists(filename):632log.warn(633"WARNING: 'depends.txt' is not used by setuptools 0.6!\n"634"Use the install_requires/extras_require setup() args instead."635)636637638def _write_requirements(stream, reqs):639lines = yield_lines(reqs or ())640641def append_cr(line):642return line + '\n'643lines = map(append_cr, lines)644stream.writelines(lines)645646647def write_requirements(cmd, basename, filename):648dist = cmd.distribution649data = six.StringIO()650_write_requirements(data, dist.install_requires)651extras_require = dist.extras_require or {}652for extra in sorted(extras_require):653data.write('\n[{extra}]\n'.format(**vars()))654_write_requirements(data, extras_require[extra])655cmd.write_or_delete_file("requirements", filename, data.getvalue())656657658def write_setup_requirements(cmd, basename, filename):659data = io.StringIO()660_write_requirements(data, cmd.distribution.setup_requires)661cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())662663664def write_toplevel_names(cmd, basename, filename):665pkgs = dict.fromkeys(666[667k.split('.', 1)[0]668for k in cmd.distribution.iter_distribution_names()669]670)671cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')672673674def overwrite_arg(cmd, basename, filename):675write_arg(cmd, basename, filename, True)676677678def write_arg(cmd, basename, filename, force=False):679argname = os.path.splitext(basename)[0]680value = getattr(cmd.distribution, argname, None)681if value is not None:682value = '\n'.join(value) + '\n'683cmd.write_or_delete_file(argname, filename, value, force)684685686def write_entries(cmd, basename, filename):687ep = cmd.distribution.entry_points688689if isinstance(ep, six.string_types) or ep is None:690data = ep691elif ep is not None:692data = []693for section, contents in sorted(ep.items()):694if not isinstance(contents, six.string_types):695contents = EntryPoint.parse_group(section, contents)696contents = '\n'.join(sorted(map(str, contents.values())))697data.append('[%s]\n%s\n\n' % (section, contents))698data = ''.join(data)699700cmd.write_or_delete_file('entry points', filename, data, True)701702703def get_pkg_info_revision():704"""705Get a -r### off of PKG-INFO Version in case this is an sdist of706a subversion revision.707"""708warnings.warn(709"get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)710if os.path.exists('PKG-INFO'):711with io.open('PKG-INFO') as f:712for line in f:713match = re.match(r"Version:.*-r(\d+)\s*$", line)714if match:715return int(match.group(1))716return 0717718719class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):720"""Deprecated behavior warning for EggInfo, bypassing suppression."""721722723