Spaces:
Runtime error
Runtime error
| """setuptools.command.egg_info | |
| Create a distribution's .egg-info directory and contents""" | |
| from distutils.filelist import FileList as _FileList | |
| from distutils.errors import DistutilsInternalError | |
| from distutils.util import convert_path | |
| from distutils import log | |
| import distutils.errors | |
| import distutils.filelist | |
| import functools | |
| import os | |
| import re | |
| import sys | |
| import io | |
| import warnings | |
| import time | |
| import collections | |
| from setuptools import Command | |
| from setuptools.command.sdist import sdist | |
| from setuptools.command.sdist import walk_revctrl | |
| from setuptools.command.setopt import edit_config | |
| from setuptools.command import bdist_egg | |
| from pkg_resources import ( | |
| parse_requirements, safe_name, parse_version, | |
| safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) | |
| import setuptools.unicode_utils as unicode_utils | |
| from setuptools.glob import glob | |
| from setuptools.extern import packaging | |
| from setuptools import SetuptoolsDeprecationWarning | |
| def translate_pattern(glob): # noqa: C901 # is too complex (14) # FIXME | |
| """ | |
| Translate a file path glob like '*.txt' in to a regular expression. | |
| This differs from fnmatch.translate which allows wildcards to match | |
| directory separators. It also knows about '**/' which matches any number of | |
| directories. | |
| """ | |
| pat = '' | |
| # This will split on '/' within [character classes]. This is deliberate. | |
| chunks = glob.split(os.path.sep) | |
| sep = re.escape(os.sep) | |
| valid_char = '[^%s]' % (sep,) | |
| for c, chunk in enumerate(chunks): | |
| last_chunk = c == len(chunks) - 1 | |
| # Chunks that are a literal ** are globstars. They match anything. | |
| if chunk == '**': | |
| if last_chunk: | |
| # Match anything if this is the last component | |
| pat += '.*' | |
| else: | |
| # Match '(name/)*' | |
| pat += '(?:%s+%s)*' % (valid_char, sep) | |
| continue # Break here as the whole path component has been handled | |
| # Find any special characters in the remainder | |
| i = 0 | |
| chunk_len = len(chunk) | |
| while i < chunk_len: | |
| char = chunk[i] | |
| if char == '*': | |
| # Match any number of name characters | |
| pat += valid_char + '*' | |
| elif char == '?': | |
| # Match a name character | |
| pat += valid_char | |
| elif char == '[': | |
| # Character class | |
| inner_i = i + 1 | |
| # Skip initial !/] chars | |
| if inner_i < chunk_len and chunk[inner_i] == '!': | |
| inner_i = inner_i + 1 | |
| if inner_i < chunk_len and chunk[inner_i] == ']': | |
| inner_i = inner_i + 1 | |
| # Loop till the closing ] is found | |
| while inner_i < chunk_len and chunk[inner_i] != ']': | |
| inner_i = inner_i + 1 | |
| if inner_i >= chunk_len: | |
| # Got to the end of the string without finding a closing ] | |
| # Do not treat this as a matching group, but as a literal [ | |
| pat += re.escape(char) | |
| else: | |
| # Grab the insides of the [brackets] | |
| inner = chunk[i + 1:inner_i] | |
| char_class = '' | |
| # Class negation | |
| if inner[0] == '!': | |
| char_class = '^' | |
| inner = inner[1:] | |
| char_class += re.escape(inner) | |
| pat += '[%s]' % (char_class,) | |
| # Skip to the end ] | |
| i = inner_i | |
| else: | |
| pat += re.escape(char) | |
| i += 1 | |
| # Join each chunk with the dir separator | |
| if not last_chunk: | |
| pat += sep | |
| pat += r'\Z' | |
| return re.compile(pat, flags=re.MULTILINE | re.DOTALL) | |
| class InfoCommon: | |
| tag_build = None | |
| tag_date = None | |
| def name(self): | |
| return safe_name(self.distribution.get_name()) | |
| def tagged_version(self): | |
| return safe_version(self._maybe_tag(self.distribution.get_version())) | |
| def _maybe_tag(self, version): | |
| """ | |
| egg_info may be called more than once for a distribution, | |
| in which case the version string already contains all tags. | |
| """ | |
| return ( | |
| version if self.vtags and version.endswith(self.vtags) | |
| else version + self.vtags | |
| ) | |
| def tags(self): | |
| version = '' | |
| if self.tag_build: | |
| version += self.tag_build | |
| if self.tag_date: | |
| version += time.strftime("-%Y%m%d") | |
| return version | |
| vtags = property(tags) | |
| class egg_info(InfoCommon, Command): | |
| description = "create a distribution's .egg-info directory" | |
| user_options = [ | |
| ('egg-base=', 'e', "directory containing .egg-info directories" | |
| " (default: top of the source tree)"), | |
| ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), | |
| ('tag-build=', 'b', "Specify explicit tag to add to version number"), | |
| ('no-date', 'D', "Don't include date stamp [default]"), | |
| ] | |
| boolean_options = ['tag-date'] | |
| negative_opt = { | |
| 'no-date': 'tag-date', | |
| } | |
| def initialize_options(self): | |
| self.egg_base = None | |
| self.egg_name = None | |
| self.egg_info = None | |
| self.egg_version = None | |
| self.broken_egg_info = False | |
| #################################### | |
| # allow the 'tag_svn_revision' to be detected and | |
| # set, supporting sdists built on older Setuptools. | |
| def tag_svn_revision(self): | |
| pass | |
| def tag_svn_revision(self, value): | |
| pass | |
| #################################### | |
| def save_version_info(self, filename): | |
| """ | |
| Materialize the value of date into the | |
| build tag. Install build keys in a deterministic order | |
| to avoid arbitrary reordering on subsequent builds. | |
| """ | |
| egg_info = collections.OrderedDict() | |
| # follow the order these keys would have been added | |
| # when PYTHONHASHSEED=0 | |
| egg_info['tag_build'] = self.tags() | |
| egg_info['tag_date'] = 0 | |
| edit_config(filename, dict(egg_info=egg_info)) | |
| def finalize_options(self): | |
| # Note: we need to capture the current value returned | |
| # by `self.tagged_version()`, so we can later update | |
| # `self.distribution.metadata.version` without | |
| # repercussions. | |
| self.egg_name = self.name | |
| self.egg_version = self.tagged_version() | |
| parsed_version = parse_version(self.egg_version) | |
| try: | |
| is_version = isinstance(parsed_version, packaging.version.Version) | |
| spec = ( | |
| "%s==%s" if is_version else "%s===%s" | |
| ) | |
| list( | |
| parse_requirements(spec % (self.egg_name, self.egg_version)) | |
| ) | |
| except ValueError as e: | |
| raise distutils.errors.DistutilsOptionError( | |
| "Invalid distribution name or version syntax: %s-%s" % | |
| (self.egg_name, self.egg_version) | |
| ) from e | |
| if self.egg_base is None: | |
| dirs = self.distribution.package_dir | |
| self.egg_base = (dirs or {}).get('', os.curdir) | |
| self.ensure_dirname('egg_base') | |
| self.egg_info = to_filename(self.egg_name) + '.egg-info' | |
| if self.egg_base != os.curdir: | |
| self.egg_info = os.path.join(self.egg_base, self.egg_info) | |
| if '-' in self.egg_name: | |
| self.check_broken_egg_info() | |
| # Set package version for the benefit of dumber commands | |
| # (e.g. sdist, bdist_wininst, etc.) | |
| # | |
| self.distribution.metadata.version = self.egg_version | |
| # If we bootstrapped around the lack of a PKG-INFO, as might be the | |
| # case in a fresh checkout, make sure that any special tags get added | |
| # to the version info | |
| # | |
| pd = self.distribution._patched_dist | |
| if pd is not None and pd.key == self.egg_name.lower(): | |
| pd._version = self.egg_version | |
| pd._parsed_version = parse_version(self.egg_version) | |
| self.distribution._patched_dist = None | |
| def write_or_delete_file(self, what, filename, data, force=False): | |
| """Write `data` to `filename` or delete if empty | |
| If `data` is non-empty, this routine is the same as ``write_file()``. | |
| If `data` is empty but not ``None``, this is the same as calling | |
| ``delete_file(filename)`. If `data` is ``None``, then this is a no-op | |
| unless `filename` exists, in which case a warning is issued about the | |
| orphaned file (if `force` is false), or deleted (if `force` is true). | |
| """ | |
| if data: | |
| self.write_file(what, filename, data) | |
| elif os.path.exists(filename): | |
| if data is None and not force: | |
| log.warn( | |
| "%s not set in setup(), but %s exists", what, filename | |
| ) | |
| return | |
| else: | |
| self.delete_file(filename) | |
| def write_file(self, what, filename, data): | |
| """Write `data` to `filename` (if not a dry run) after announcing it | |
| `what` is used in a log message to identify what is being written | |
| to the file. | |
| """ | |
| log.info("writing %s to %s", what, filename) | |
| data = data.encode("utf-8") | |
| if not self.dry_run: | |
| f = open(filename, 'wb') | |
| f.write(data) | |
| f.close() | |
| def delete_file(self, filename): | |
| """Delete `filename` (if not a dry run) after announcing it""" | |
| log.info("deleting %s", filename) | |
| if not self.dry_run: | |
| os.unlink(filename) | |
| def run(self): | |
| self.mkpath(self.egg_info) | |
| os.utime(self.egg_info, None) | |
| installer = self.distribution.fetch_build_egg | |
| for ep in iter_entry_points('egg_info.writers'): | |
| ep.require(installer=installer) | |
| writer = ep.resolve() | |
| writer(self, ep.name, os.path.join(self.egg_info, ep.name)) | |
| # Get rid of native_libs.txt if it was put there by older bdist_egg | |
| nl = os.path.join(self.egg_info, "native_libs.txt") | |
| if os.path.exists(nl): | |
| self.delete_file(nl) | |
| self.find_sources() | |
| def find_sources(self): | |
| """Generate SOURCES.txt manifest file""" | |
| manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") | |
| mm = manifest_maker(self.distribution) | |
| mm.manifest = manifest_filename | |
| mm.run() | |
| self.filelist = mm.filelist | |
| def check_broken_egg_info(self): | |
| bei = self.egg_name + '.egg-info' | |
| if self.egg_base != os.curdir: | |
| bei = os.path.join(self.egg_base, bei) | |
| if os.path.exists(bei): | |
| log.warn( | |
| "-" * 78 + '\n' | |
| "Note: Your current .egg-info directory has a '-' in its name;" | |
| '\nthis will not work correctly with "setup.py develop".\n\n' | |
| 'Please rename %s to %s to correct this problem.\n' + '-' * 78, | |
| bei, self.egg_info | |
| ) | |
| self.broken_egg_info = self.egg_info | |
| self.egg_info = bei # make it work for now | |
| class FileList(_FileList): | |
| # Implementations of the various MANIFEST.in commands | |
| def process_template_line(self, line): | |
| # Parse the line: split it up, make sure the right number of words | |
| # is there, and return the relevant words. 'action' is always | |
| # defined: it's the first word of the line. Which of the other | |
| # three are defined depends on the action; it'll be either | |
| # patterns, (dir and patterns), or (dir_pattern). | |
| (action, patterns, dir, dir_pattern) = self._parse_template_line(line) | |
| action_map = { | |
| 'include': self.include, | |
| 'exclude': self.exclude, | |
| 'global-include': self.global_include, | |
| 'global-exclude': self.global_exclude, | |
| 'recursive-include': functools.partial( | |
| self.recursive_include, dir, | |
| ), | |
| 'recursive-exclude': functools.partial( | |
| self.recursive_exclude, dir, | |
| ), | |
| 'graft': self.graft, | |
| 'prune': self.prune, | |
| } | |
| log_map = { | |
| 'include': "warning: no files found matching '%s'", | |
| 'exclude': ( | |
| "warning: no previously-included files found " | |
| "matching '%s'" | |
| ), | |
| 'global-include': ( | |
| "warning: no files found matching '%s' " | |
| "anywhere in distribution" | |
| ), | |
| 'global-exclude': ( | |
| "warning: no previously-included files matching " | |
| "'%s' found anywhere in distribution" | |
| ), | |
| 'recursive-include': ( | |
| "warning: no files found matching '%s' " | |
| "under directory '%s'" | |
| ), | |
| 'recursive-exclude': ( | |
| "warning: no previously-included files matching " | |
| "'%s' found under directory '%s'" | |
| ), | |
| 'graft': "warning: no directories found matching '%s'", | |
| 'prune': "no previously-included directories found matching '%s'", | |
| } | |
| try: | |
| process_action = action_map[action] | |
| except KeyError: | |
| raise DistutilsInternalError( | |
| "this cannot happen: invalid action '{action!s}'". | |
| format(action=action), | |
| ) | |
| # OK, now we know that the action is valid and we have the | |
| # right number of words on the line for that action -- so we | |
| # can proceed with minimal error-checking. | |
| action_is_recursive = action.startswith('recursive-') | |
| if action in {'graft', 'prune'}: | |
| patterns = [dir_pattern] | |
| extra_log_args = (dir, ) if action_is_recursive else () | |
| log_tmpl = log_map[action] | |
| self.debug_print( | |
| ' '.join( | |
| [action] + | |
| ([dir] if action_is_recursive else []) + | |
| patterns, | |
| ) | |
| ) | |
| for pattern in patterns: | |
| if not process_action(pattern): | |
| log.warn(log_tmpl, pattern, *extra_log_args) | |
| def _remove_files(self, predicate): | |
| """ | |
| Remove all files from the file list that match the predicate. | |
| Return True if any matching files were removed | |
| """ | |
| found = False | |
| for i in range(len(self.files) - 1, -1, -1): | |
| if predicate(self.files[i]): | |
| self.debug_print(" removing " + self.files[i]) | |
| del self.files[i] | |
| found = True | |
| return found | |
| def include(self, pattern): | |
| """Include files that match 'pattern'.""" | |
| found = [f for f in glob(pattern) if not os.path.isdir(f)] | |
| self.extend(found) | |
| return bool(found) | |
| def exclude(self, pattern): | |
| """Exclude files that match 'pattern'.""" | |
| match = translate_pattern(pattern) | |
| return self._remove_files(match.match) | |
| def recursive_include(self, dir, pattern): | |
| """ | |
| Include all files anywhere in 'dir/' that match the pattern. | |
| """ | |
| full_pattern = os.path.join(dir, '**', pattern) | |
| found = [f for f in glob(full_pattern, recursive=True) | |
| if not os.path.isdir(f)] | |
| self.extend(found) | |
| return bool(found) | |
| def recursive_exclude(self, dir, pattern): | |
| """ | |
| Exclude any file anywhere in 'dir/' that match the pattern. | |
| """ | |
| match = translate_pattern(os.path.join(dir, '**', pattern)) | |
| return self._remove_files(match.match) | |
| def graft(self, dir): | |
| """Include all files from 'dir/'.""" | |
| found = [ | |
| item | |
| for match_dir in glob(dir) | |
| for item in distutils.filelist.findall(match_dir) | |
| ] | |
| self.extend(found) | |
| return bool(found) | |
| def prune(self, dir): | |
| """Filter out files from 'dir/'.""" | |
| match = translate_pattern(os.path.join(dir, '**')) | |
| return self._remove_files(match.match) | |
| def global_include(self, pattern): | |
| """ | |
| Include all files anywhere in the current directory that match the | |
| pattern. This is very inefficient on large file trees. | |
| """ | |
| if self.allfiles is None: | |
| self.findall() | |
| match = translate_pattern(os.path.join('**', pattern)) | |
| found = [f for f in self.allfiles if match.match(f)] | |
| self.extend(found) | |
| return bool(found) | |
| def global_exclude(self, pattern): | |
| """ | |
| Exclude all files anywhere that match the pattern. | |
| """ | |
| match = translate_pattern(os.path.join('**', pattern)) | |
| return self._remove_files(match.match) | |
| def append(self, item): | |
| if item.endswith('\r'): # Fix older sdists built on Windows | |
| item = item[:-1] | |
| path = convert_path(item) | |
| if self._safe_path(path): | |
| self.files.append(path) | |
| def extend(self, paths): | |
| self.files.extend(filter(self._safe_path, paths)) | |
| def _repair(self): | |
| """ | |
| Replace self.files with only safe paths | |
| Because some owners of FileList manipulate the underlying | |
| ``files`` attribute directly, this method must be called to | |
| repair those paths. | |
| """ | |
| self.files = list(filter(self._safe_path, self.files)) | |
| def _safe_path(self, path): | |
| enc_warn = "'%s' not %s encodable -- skipping" | |
| # To avoid accidental trans-codings errors, first to unicode | |
| u_path = unicode_utils.filesys_decode(path) | |
| if u_path is None: | |
| log.warn("'%s' in unexpected encoding -- skipping" % path) | |
| return False | |
| # Must ensure utf-8 encodability | |
| utf8_path = unicode_utils.try_encode(u_path, "utf-8") | |
| if utf8_path is None: | |
| log.warn(enc_warn, path, 'utf-8') | |
| return False | |
| try: | |
| # accept is either way checks out | |
| if os.path.exists(u_path) or os.path.exists(utf8_path): | |
| return True | |
| # this will catch any encode errors decoding u_path | |
| except UnicodeEncodeError: | |
| log.warn(enc_warn, path, sys.getfilesystemencoding()) | |
| class manifest_maker(sdist): | |
| template = "MANIFEST.in" | |
| def initialize_options(self): | |
| self.use_defaults = 1 | |
| self.prune = 1 | |
| self.manifest_only = 1 | |
| self.force_manifest = 1 | |
| def finalize_options(self): | |
| pass | |
| def run(self): | |
| self.filelist = FileList() | |
| if not os.path.exists(self.manifest): | |
| self.write_manifest() # it must exist so it'll get in the list | |
| self.add_defaults() | |
| if os.path.exists(self.template): | |
| self.read_template() | |
| self.add_license_files() | |
| self.prune_file_list() | |
| self.filelist.sort() | |
| self.filelist.remove_duplicates() | |
| self.write_manifest() | |
| def _manifest_normalize(self, path): | |
| path = unicode_utils.filesys_decode(path) | |
| return path.replace(os.sep, '/') | |
| def write_manifest(self): | |
| """ | |
| Write the file list in 'self.filelist' to the manifest file | |
| named by 'self.manifest'. | |
| """ | |
| self.filelist._repair() | |
| # Now _repairs should encodability, but not unicode | |
| files = [self._manifest_normalize(f) for f in self.filelist.files] | |
| msg = "writing manifest file '%s'" % self.manifest | |
| self.execute(write_file, (self.manifest, files), msg) | |
| def warn(self, msg): | |
| if not self._should_suppress_warning(msg): | |
| sdist.warn(self, msg) | |
| def _should_suppress_warning(msg): | |
| """ | |
| suppress missing-file warnings from sdist | |
| """ | |
| return re.match(r"standard file .*not found", msg) | |
| def add_defaults(self): | |
| sdist.add_defaults(self) | |
| self.filelist.append(self.template) | |
| self.filelist.append(self.manifest) | |
| rcfiles = list(walk_revctrl()) | |
| if rcfiles: | |
| self.filelist.extend(rcfiles) | |
| elif os.path.exists(self.manifest): | |
| self.read_manifest() | |
| if os.path.exists("setup.py"): | |
| # setup.py should be included by default, even if it's not | |
| # the script called to create the sdist | |
| self.filelist.append("setup.py") | |
| ei_cmd = self.get_finalized_command('egg_info') | |
| self.filelist.graft(ei_cmd.egg_info) | |
| def add_license_files(self): | |
| license_files = self.distribution.metadata.license_files or [] | |
| for lf in license_files: | |
| log.info("adding license file '%s'", lf) | |
| pass | |
| self.filelist.extend(license_files) | |
| def prune_file_list(self): | |
| build = self.get_finalized_command('build') | |
| base_dir = self.distribution.get_fullname() | |
| self.filelist.prune(build.build_base) | |
| self.filelist.prune(base_dir) | |
| sep = re.escape(os.sep) | |
| self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, | |
| is_regex=1) | |
| def write_file(filename, contents): | |
| """Create a file with the specified name and write 'contents' (a | |
| sequence of strings without line terminators) to it. | |
| """ | |
| contents = "\n".join(contents) | |
| # assuming the contents has been vetted for utf-8 encoding | |
| contents = contents.encode("utf-8") | |
| with open(filename, "wb") as f: # always write POSIX-style manifest | |
| f.write(contents) | |
| def write_pkg_info(cmd, basename, filename): | |
| log.info("writing %s", filename) | |
| if not cmd.dry_run: | |
| metadata = cmd.distribution.metadata | |
| metadata.version, oldver = cmd.egg_version, metadata.version | |
| metadata.name, oldname = cmd.egg_name, metadata.name | |
| try: | |
| # write unescaped data to PKG-INFO, so older pkg_resources | |
| # can still parse it | |
| metadata.write_pkg_info(cmd.egg_info) | |
| finally: | |
| metadata.name, metadata.version = oldname, oldver | |
| safe = getattr(cmd.distribution, 'zip_safe', None) | |
| bdist_egg.write_safety_flag(cmd.egg_info, safe) | |
| def warn_depends_obsolete(cmd, basename, filename): | |
| if os.path.exists(filename): | |
| log.warn( | |
| "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" | |
| "Use the install_requires/extras_require setup() args instead." | |
| ) | |
| def _write_requirements(stream, reqs): | |
| lines = yield_lines(reqs or ()) | |
| def append_cr(line): | |
| return line + '\n' | |
| lines = map(append_cr, lines) | |
| stream.writelines(lines) | |
| def write_requirements(cmd, basename, filename): | |
| dist = cmd.distribution | |
| data = io.StringIO() | |
| _write_requirements(data, dist.install_requires) | |
| extras_require = dist.extras_require or {} | |
| for extra in sorted(extras_require): | |
| data.write('\n[{extra}]\n'.format(**vars())) | |
| _write_requirements(data, extras_require[extra]) | |
| cmd.write_or_delete_file("requirements", filename, data.getvalue()) | |
| def write_setup_requirements(cmd, basename, filename): | |
| data = io.StringIO() | |
| _write_requirements(data, cmd.distribution.setup_requires) | |
| cmd.write_or_delete_file("setup-requirements", filename, data.getvalue()) | |
| def write_toplevel_names(cmd, basename, filename): | |
| pkgs = dict.fromkeys( | |
| [ | |
| k.split('.', 1)[0] | |
| for k in cmd.distribution.iter_distribution_names() | |
| ] | |
| ) | |
| cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') | |
| def overwrite_arg(cmd, basename, filename): | |
| write_arg(cmd, basename, filename, True) | |
| def write_arg(cmd, basename, filename, force=False): | |
| argname = os.path.splitext(basename)[0] | |
| value = getattr(cmd.distribution, argname, None) | |
| if value is not None: | |
| value = '\n'.join(value) + '\n' | |
| cmd.write_or_delete_file(argname, filename, value, force) | |
| def write_entries(cmd, basename, filename): | |
| ep = cmd.distribution.entry_points | |
| if isinstance(ep, str) or ep is None: | |
| data = ep | |
| elif ep is not None: | |
| data = [] | |
| for section, contents in sorted(ep.items()): | |
| if not isinstance(contents, str): | |
| contents = EntryPoint.parse_group(section, contents) | |
| contents = '\n'.join(sorted(map(str, contents.values()))) | |
| data.append('[%s]\n%s\n\n' % (section, contents)) | |
| data = ''.join(data) | |
| cmd.write_or_delete_file('entry points', filename, data, True) | |
| def get_pkg_info_revision(): | |
| """ | |
| Get a -r### off of PKG-INFO Version in case this is an sdist of | |
| a subversion revision. | |
| """ | |
| warnings.warn( | |
| "get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) | |
| if os.path.exists('PKG-INFO'): | |
| with io.open('PKG-INFO') as f: | |
| for line in f: | |
| match = re.match(r"Version:.*-r(\d+)\s*$", line) | |
| if match: | |
| return int(match.group(1)) | |
| return 0 | |
| class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): | |
| """Deprecated behavior warning for EggInfo, bypassing suppression.""" | |