| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734 | """setuptools.command.egg_infoCreate a distribution's .egg-info directory and contents"""from distutils.filelist import FileList as _FileListfrom distutils.errors import DistutilsInternalErrorfrom distutils.util import convert_pathfrom distutils import logimport distutils.errorsimport distutils.filelistimport functoolsimport osimport reimport sysimport ioimport warningsimport timeimport collectionsfrom setuptools import Commandfrom setuptools.command.sdist import sdistfrom setuptools.command.sdist import walk_revctrlfrom setuptools.command.setopt import edit_configfrom setuptools.command import bdist_eggfrom 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_utilsfrom setuptools.glob import globfrom setuptools.extern import packagingfrom setuptools import SetuptoolsDeprecationWarningdef 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    @property    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.    @property    def tag_svn_revision(self):        pass    @tag_svn_revision.setter    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 nowclass 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)    @staticmethod    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 0class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):    """Deprecated behavior warning for EggInfo, bypassing suppression."""
 |