]> jfr.im git - dlqueue.git/blame - venv/lib/python3.11/site-packages/setuptools/command/egg_info.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / setuptools / command / egg_info.py
CommitLineData
e0df8241
JR
1"""setuptools.command.egg_info
2
3Create a distribution's .egg-info directory and contents"""
4
5from distutils.filelist import FileList as _FileList
6from distutils.errors import DistutilsInternalError
7from distutils.util import convert_path
8from distutils import log
9import distutils.errors
10import distutils.filelist
11import functools
12import os
13import re
14import sys
15import io
16import time
17import collections
18
19from .._importlib import metadata
20from .. import _entry_points, _normalization
21
22from setuptools import Command
23from setuptools.command.sdist import sdist
24from setuptools.command.sdist import walk_revctrl
25from setuptools.command.setopt import edit_config
26from setuptools.command import bdist_egg
27import setuptools.unicode_utils as unicode_utils
28from setuptools.glob import glob
29
30from setuptools.extern import packaging
31from setuptools.extern.jaraco.text import yield_lines
32from ..warnings import SetuptoolsDeprecationWarning
33
34
35PY_MAJOR = '{}.{}'.format(*sys.version_info)
36
37
38def translate_pattern(glob): # noqa: C901 # is too complex (14) # FIXME
39 """
40 Translate a file path glob like '*.txt' in to a regular expression.
41 This differs from fnmatch.translate which allows wildcards to match
42 directory separators. It also knows about '**/' which matches any number of
43 directories.
44 """
45 pat = ''
46
47 # This will split on '/' within [character classes]. This is deliberate.
48 chunks = glob.split(os.path.sep)
49
50 sep = re.escape(os.sep)
51 valid_char = '[^%s]' % (sep,)
52
53 for c, chunk in enumerate(chunks):
54 last_chunk = c == len(chunks) - 1
55
56 # Chunks that are a literal ** are globstars. They match anything.
57 if chunk == '**':
58 if last_chunk:
59 # Match anything if this is the last component
60 pat += '.*'
61 else:
62 # Match '(name/)*'
63 pat += '(?:%s+%s)*' % (valid_char, sep)
64 continue # Break here as the whole path component has been handled
65
66 # Find any special characters in the remainder
67 i = 0
68 chunk_len = len(chunk)
69 while i < chunk_len:
70 char = chunk[i]
71 if char == '*':
72 # Match any number of name characters
73 pat += valid_char + '*'
74 elif char == '?':
75 # Match a name character
76 pat += valid_char
77 elif char == '[':
78 # Character class
79 inner_i = i + 1
80 # Skip initial !/] chars
81 if inner_i < chunk_len and chunk[inner_i] == '!':
82 inner_i = inner_i + 1
83 if inner_i < chunk_len and chunk[inner_i] == ']':
84 inner_i = inner_i + 1
85
86 # Loop till the closing ] is found
87 while inner_i < chunk_len and chunk[inner_i] != ']':
88 inner_i = inner_i + 1
89
90 if inner_i >= chunk_len:
91 # Got to the end of the string without finding a closing ]
92 # Do not treat this as a matching group, but as a literal [
93 pat += re.escape(char)
94 else:
95 # Grab the insides of the [brackets]
96 inner = chunk[i + 1 : inner_i]
97 char_class = ''
98
99 # Class negation
100 if inner[0] == '!':
101 char_class = '^'
102 inner = inner[1:]
103
104 char_class += re.escape(inner)
105 pat += '[%s]' % (char_class,)
106
107 # Skip to the end ]
108 i = inner_i
109 else:
110 pat += re.escape(char)
111 i += 1
112
113 # Join each chunk with the dir separator
114 if not last_chunk:
115 pat += sep
116
117 pat += r'\Z'
118 return re.compile(pat, flags=re.MULTILINE | re.DOTALL)
119
120
121class InfoCommon:
122 tag_build = None
123 tag_date = None
124
125 @property
126 def name(self):
127 return _normalization.safe_name(self.distribution.get_name())
128
129 def tagged_version(self):
130 tagged = self._maybe_tag(self.distribution.get_version())
131 return _normalization.best_effort_version(tagged)
132
133 def _maybe_tag(self, version):
134 """
135 egg_info may be called more than once for a distribution,
136 in which case the version string already contains all tags.
137 """
138 return (
139 version
140 if self.vtags and self._already_tagged(version)
141 else version + self.vtags
142 )
143
144 def _already_tagged(self, version: str) -> bool:
145 # Depending on their format, tags may change with version normalization.
146 # So in addition the regular tags, we have to search for the normalized ones.
147 return version.endswith(self.vtags) or version.endswith(self._safe_tags())
148
149 def _safe_tags(self) -> str:
150 # To implement this we can rely on `safe_version` pretending to be version 0
151 # followed by tags. Then we simply discard the starting 0 (fake version number)
152 return _normalization.best_effort_version(f"0{self.vtags}")[1:]
153
154 def tags(self) -> str:
155 version = ''
156 if self.tag_build:
157 version += self.tag_build
158 if self.tag_date:
159 version += time.strftime("%Y%m%d")
160 return version
161
162 vtags = property(tags)
163
164
165class egg_info(InfoCommon, Command):
166 description = "create a distribution's .egg-info directory"
167
168 user_options = [
169 (
170 'egg-base=',
171 'e',
172 "directory containing .egg-info directories"
173 " (default: top of the source tree)",
174 ),
175 ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
176 ('tag-build=', 'b', "Specify explicit tag to add to version number"),
177 ('no-date', 'D', "Don't include date stamp [default]"),
178 ]
179
180 boolean_options = ['tag-date']
181 negative_opt = {
182 'no-date': 'tag-date',
183 }
184
185 def initialize_options(self):
186 self.egg_base = None
187 self.egg_name = None
188 self.egg_info = None
189 self.egg_version = None
190 self.ignore_egg_info_in_manifest = False
191
192 ####################################
193 # allow the 'tag_svn_revision' to be detected and
194 # set, supporting sdists built on older Setuptools.
195 @property
196 def tag_svn_revision(self):
197 pass
198
199 @tag_svn_revision.setter
200 def tag_svn_revision(self, value):
201 pass
202
203 ####################################
204
205 def save_version_info(self, filename):
206 """
207 Materialize the value of date into the
208 build tag. Install build keys in a deterministic order
209 to avoid arbitrary reordering on subsequent builds.
210 """
211 egg_info = collections.OrderedDict()
212 # follow the order these keys would have been added
213 # when PYTHONHASHSEED=0
214 egg_info['tag_build'] = self.tags()
215 egg_info['tag_date'] = 0
216 edit_config(filename, dict(egg_info=egg_info))
217
218 def finalize_options(self):
219 # Note: we need to capture the current value returned
220 # by `self.tagged_version()`, so we can later update
221 # `self.distribution.metadata.version` without
222 # repercussions.
223 self.egg_name = self.name
224 self.egg_version = self.tagged_version()
225 parsed_version = packaging.version.Version(self.egg_version)
226
227 try:
228 is_version = isinstance(parsed_version, packaging.version.Version)
229 spec = "%s==%s" if is_version else "%s===%s"
230 packaging.requirements.Requirement(spec % (self.egg_name, self.egg_version))
231 except ValueError as e:
232 raise distutils.errors.DistutilsOptionError(
233 "Invalid distribution name or version syntax: %s-%s"
234 % (self.egg_name, self.egg_version)
235 ) from e
236
237 if self.egg_base is None:
238 dirs = self.distribution.package_dir
239 self.egg_base = (dirs or {}).get('', os.curdir)
240
241 self.ensure_dirname('egg_base')
242 self.egg_info = _normalization.filename_component(self.egg_name) + '.egg-info'
243 if self.egg_base != os.curdir:
244 self.egg_info = os.path.join(self.egg_base, self.egg_info)
245
246 # Set package version for the benefit of dumber commands
247 # (e.g. sdist, bdist_wininst, etc.)
248 #
249 self.distribution.metadata.version = self.egg_version
250
251 # If we bootstrapped around the lack of a PKG-INFO, as might be the
252 # case in a fresh checkout, make sure that any special tags get added
253 # to the version info
254 #
255 pd = self.distribution._patched_dist
256 key = getattr(pd, "key", None) or getattr(pd, "name", None)
257 if pd is not None and key == self.egg_name.lower():
258 pd._version = self.egg_version
259 pd._parsed_version = packaging.version.Version(self.egg_version)
260 self.distribution._patched_dist = None
261
262 def _get_egg_basename(self, py_version=PY_MAJOR, platform=None):
263 """Compute filename of the output egg. Private API."""
264 return _egg_basename(self.egg_name, self.egg_version, py_version, platform)
265
266 def write_or_delete_file(self, what, filename, data, force=False):
267 """Write `data` to `filename` or delete if empty
268
269 If `data` is non-empty, this routine is the same as ``write_file()``.
270 If `data` is empty but not ``None``, this is the same as calling
271 ``delete_file(filename)`. If `data` is ``None``, then this is a no-op
272 unless `filename` exists, in which case a warning is issued about the
273 orphaned file (if `force` is false), or deleted (if `force` is true).
274 """
275 if data:
276 self.write_file(what, filename, data)
277 elif os.path.exists(filename):
278 if data is None and not force:
279 log.warn("%s not set in setup(), but %s exists", what, filename)
280 return
281 else:
282 self.delete_file(filename)
283
284 def write_file(self, what, filename, data):
285 """Write `data` to `filename` (if not a dry run) after announcing it
286
287 `what` is used in a log message to identify what is being written
288 to the file.
289 """
290 log.info("writing %s to %s", what, filename)
291 data = data.encode("utf-8")
292 if not self.dry_run:
293 f = open(filename, 'wb')
294 f.write(data)
295 f.close()
296
297 def delete_file(self, filename):
298 """Delete `filename` (if not a dry run) after announcing it"""
299 log.info("deleting %s", filename)
300 if not self.dry_run:
301 os.unlink(filename)
302
303 def run(self):
304 self.mkpath(self.egg_info)
305 try:
306 os.utime(self.egg_info, None)
307 except OSError as e:
308 msg = f"Cannot update time stamp of directory '{self.egg_info}'"
309 raise distutils.errors.DistutilsFileError(msg) from e
310 for ep in metadata.entry_points(group='egg_info.writers'):
311 writer = ep.load()
312 writer(self, ep.name, os.path.join(self.egg_info, ep.name))
313
314 # Get rid of native_libs.txt if it was put there by older bdist_egg
315 nl = os.path.join(self.egg_info, "native_libs.txt")
316 if os.path.exists(nl):
317 self.delete_file(nl)
318
319 self.find_sources()
320
321 def find_sources(self):
322 """Generate SOURCES.txt manifest file"""
323 manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
324 mm = manifest_maker(self.distribution)
325 mm.ignore_egg_info_dir = self.ignore_egg_info_in_manifest
326 mm.manifest = manifest_filename
327 mm.run()
328 self.filelist = mm.filelist
329
330
331class FileList(_FileList):
332 # Implementations of the various MANIFEST.in commands
333
334 def __init__(self, warn=None, debug_print=None, ignore_egg_info_dir=False):
335 super().__init__(warn, debug_print)
336 self.ignore_egg_info_dir = ignore_egg_info_dir
337
338 def process_template_line(self, line):
339 # Parse the line: split it up, make sure the right number of words
340 # is there, and return the relevant words. 'action' is always
341 # defined: it's the first word of the line. Which of the other
342 # three are defined depends on the action; it'll be either
343 # patterns, (dir and patterns), or (dir_pattern).
344 (action, patterns, dir, dir_pattern) = self._parse_template_line(line)
345
346 action_map = {
347 'include': self.include,
348 'exclude': self.exclude,
349 'global-include': self.global_include,
350 'global-exclude': self.global_exclude,
351 'recursive-include': functools.partial(
352 self.recursive_include,
353 dir,
354 ),
355 'recursive-exclude': functools.partial(
356 self.recursive_exclude,
357 dir,
358 ),
359 'graft': self.graft,
360 'prune': self.prune,
361 }
362 log_map = {
363 'include': "warning: no files found matching '%s'",
364 'exclude': ("warning: no previously-included files found " "matching '%s'"),
365 'global-include': (
366 "warning: no files found matching '%s' " "anywhere in distribution"
367 ),
368 'global-exclude': (
369 "warning: no previously-included files matching "
370 "'%s' found anywhere in distribution"
371 ),
372 'recursive-include': (
373 "warning: no files found matching '%s' " "under directory '%s'"
374 ),
375 'recursive-exclude': (
376 "warning: no previously-included files matching "
377 "'%s' found under directory '%s'"
378 ),
379 'graft': "warning: no directories found matching '%s'",
380 'prune': "no previously-included directories found matching '%s'",
381 }
382
383 try:
384 process_action = action_map[action]
385 except KeyError:
386 raise DistutilsInternalError(
387 "this cannot happen: invalid action '{action!s}'".format(action=action),
388 )
389
390 # OK, now we know that the action is valid and we have the
391 # right number of words on the line for that action -- so we
392 # can proceed with minimal error-checking.
393
394 action_is_recursive = action.startswith('recursive-')
395 if action in {'graft', 'prune'}:
396 patterns = [dir_pattern]
397 extra_log_args = (dir,) if action_is_recursive else ()
398 log_tmpl = log_map[action]
399
400 self.debug_print(
401 ' '.join(
402 [action] + ([dir] if action_is_recursive else []) + patterns,
403 )
404 )
405 for pattern in patterns:
406 if not process_action(pattern):
407 log.warn(log_tmpl, pattern, *extra_log_args)
408
409 def _remove_files(self, predicate):
410 """
411 Remove all files from the file list that match the predicate.
412 Return True if any matching files were removed
413 """
414 found = False
415 for i in range(len(self.files) - 1, -1, -1):
416 if predicate(self.files[i]):
417 self.debug_print(" removing " + self.files[i])
418 del self.files[i]
419 found = True
420 return found
421
422 def include(self, pattern):
423 """Include files that match 'pattern'."""
424 found = [f for f in glob(pattern) if not os.path.isdir(f)]
425 self.extend(found)
426 return bool(found)
427
428 def exclude(self, pattern):
429 """Exclude files that match 'pattern'."""
430 match = translate_pattern(pattern)
431 return self._remove_files(match.match)
432
433 def recursive_include(self, dir, pattern):
434 """
435 Include all files anywhere in 'dir/' that match the pattern.
436 """
437 full_pattern = os.path.join(dir, '**', pattern)
438 found = [f for f in glob(full_pattern, recursive=True) if not os.path.isdir(f)]
439 self.extend(found)
440 return bool(found)
441
442 def recursive_exclude(self, dir, pattern):
443 """
444 Exclude any file anywhere in 'dir/' that match the pattern.
445 """
446 match = translate_pattern(os.path.join(dir, '**', pattern))
447 return self._remove_files(match.match)
448
449 def graft(self, dir):
450 """Include all files from 'dir/'."""
451 found = [
452 item
453 for match_dir in glob(dir)
454 for item in distutils.filelist.findall(match_dir)
455 ]
456 self.extend(found)
457 return bool(found)
458
459 def prune(self, dir):
460 """Filter out files from 'dir/'."""
461 match = translate_pattern(os.path.join(dir, '**'))
462 return self._remove_files(match.match)
463
464 def global_include(self, pattern):
465 """
466 Include all files anywhere in the current directory that match the
467 pattern. This is very inefficient on large file trees.
468 """
469 if self.allfiles is None:
470 self.findall()
471 match = translate_pattern(os.path.join('**', pattern))
472 found = [f for f in self.allfiles if match.match(f)]
473 self.extend(found)
474 return bool(found)
475
476 def global_exclude(self, pattern):
477 """
478 Exclude all files anywhere that match the pattern.
479 """
480 match = translate_pattern(os.path.join('**', pattern))
481 return self._remove_files(match.match)
482
483 def append(self, item):
484 if item.endswith('\r'): # Fix older sdists built on Windows
485 item = item[:-1]
486 path = convert_path(item)
487
488 if self._safe_path(path):
489 self.files.append(path)
490
491 def extend(self, paths):
492 self.files.extend(filter(self._safe_path, paths))
493
494 def _repair(self):
495 """
496 Replace self.files with only safe paths
497
498 Because some owners of FileList manipulate the underlying
499 ``files`` attribute directly, this method must be called to
500 repair those paths.
501 """
502 self.files = list(filter(self._safe_path, self.files))
503
504 def _safe_path(self, path):
505 enc_warn = "'%s' not %s encodable -- skipping"
506
507 # To avoid accidental trans-codings errors, first to unicode
508 u_path = unicode_utils.filesys_decode(path)
509 if u_path is None:
510 log.warn("'%s' in unexpected encoding -- skipping" % path)
511 return False
512
513 # Must ensure utf-8 encodability
514 utf8_path = unicode_utils.try_encode(u_path, "utf-8")
515 if utf8_path is None:
516 log.warn(enc_warn, path, 'utf-8')
517 return False
518
519 try:
520 # ignore egg-info paths
521 is_egg_info = ".egg-info" in u_path or b".egg-info" in utf8_path
522 if self.ignore_egg_info_dir and is_egg_info:
523 return False
524 # accept is either way checks out
525 if os.path.exists(u_path) or os.path.exists(utf8_path):
526 return True
527 # this will catch any encode errors decoding u_path
528 except UnicodeEncodeError:
529 log.warn(enc_warn, path, sys.getfilesystemencoding())
530
531
532class manifest_maker(sdist):
533 template = "MANIFEST.in"
534
535 def initialize_options(self):
536 self.use_defaults = 1
537 self.prune = 1
538 self.manifest_only = 1
539 self.force_manifest = 1
540 self.ignore_egg_info_dir = False
541
542 def finalize_options(self):
543 pass
544
545 def run(self):
546 self.filelist = FileList(ignore_egg_info_dir=self.ignore_egg_info_dir)
547 if not os.path.exists(self.manifest):
548 self.write_manifest() # it must exist so it'll get in the list
549 self.add_defaults()
550 if os.path.exists(self.template):
551 self.read_template()
552 self.add_license_files()
553 self._add_referenced_files()
554 self.prune_file_list()
555 self.filelist.sort()
556 self.filelist.remove_duplicates()
557 self.write_manifest()
558
559 def _manifest_normalize(self, path):
560 path = unicode_utils.filesys_decode(path)
561 return path.replace(os.sep, '/')
562
563 def write_manifest(self):
564 """
565 Write the file list in 'self.filelist' to the manifest file
566 named by 'self.manifest'.
567 """
568 self.filelist._repair()
569
570 # Now _repairs should encodability, but not unicode
571 files = [self._manifest_normalize(f) for f in self.filelist.files]
572 msg = "writing manifest file '%s'" % self.manifest
573 self.execute(write_file, (self.manifest, files), msg)
574
575 def warn(self, msg):
576 if not self._should_suppress_warning(msg):
577 sdist.warn(self, msg)
578
579 @staticmethod
580 def _should_suppress_warning(msg):
581 """
582 suppress missing-file warnings from sdist
583 """
584 return re.match(r"standard file .*not found", msg)
585
586 def add_defaults(self):
587 sdist.add_defaults(self)
588 self.filelist.append(self.template)
589 self.filelist.append(self.manifest)
590 rcfiles = list(walk_revctrl())
591 if rcfiles:
592 self.filelist.extend(rcfiles)
593 elif os.path.exists(self.manifest):
594 self.read_manifest()
595
596 if os.path.exists("setup.py"):
597 # setup.py should be included by default, even if it's not
598 # the script called to create the sdist
599 self.filelist.append("setup.py")
600
601 ei_cmd = self.get_finalized_command('egg_info')
602 self.filelist.graft(ei_cmd.egg_info)
603
604 def add_license_files(self):
605 license_files = self.distribution.metadata.license_files or []
606 for lf in license_files:
607 log.info("adding license file '%s'", lf)
608 self.filelist.extend(license_files)
609
610 def _add_referenced_files(self):
611 """Add files referenced by the config (e.g. `file:` directive) to filelist"""
612 referenced = getattr(self.distribution, '_referenced_files', [])
613 # ^-- fallback if dist comes from distutils or is a custom class
614 for rf in referenced:
615 log.debug("adding file referenced by config '%s'", rf)
616 self.filelist.extend(referenced)
617
618 def prune_file_list(self):
619 build = self.get_finalized_command('build')
620 base_dir = self.distribution.get_fullname()
621 self.filelist.prune(build.build_base)
622 self.filelist.prune(base_dir)
623 sep = re.escape(os.sep)
624 self.filelist.exclude_pattern(
625 r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, is_regex=1
626 )
627
628 def _safe_data_files(self, build_py):
629 """
630 The parent class implementation of this method
631 (``sdist``) will try to include data files, which
632 might cause recursion problems when
633 ``include_package_data=True``.
634
635 Therefore, avoid triggering any attempt of
636 analyzing/building the manifest again.
637 """
638 if hasattr(build_py, 'get_data_files_without_manifest'):
639 return build_py.get_data_files_without_manifest()
640
641 SetuptoolsDeprecationWarning.emit(
642 "`build_py` command does not inherit from setuptools' `build_py`.",
643 """
644 Custom 'build_py' does not implement 'get_data_files_without_manifest'.
645 Please extend command classes from setuptools instead of distutils.
646 """,
647 see_url="https://peps.python.org/pep-0632/",
648 # due_date not defined yet, old projects might still do it?
649 )
650 return build_py.get_data_files()
651
652
653def write_file(filename, contents):
654 """Create a file with the specified name and write 'contents' (a
655 sequence of strings without line terminators) to it.
656 """
657 contents = "\n".join(contents)
658
659 # assuming the contents has been vetted for utf-8 encoding
660 contents = contents.encode("utf-8")
661
662 with open(filename, "wb") as f: # always write POSIX-style manifest
663 f.write(contents)
664
665
666def write_pkg_info(cmd, basename, filename):
667 log.info("writing %s", filename)
668 if not cmd.dry_run:
669 metadata = cmd.distribution.metadata
670 metadata.version, oldver = cmd.egg_version, metadata.version
671 metadata.name, oldname = cmd.egg_name, metadata.name
672
673 try:
674 # write unescaped data to PKG-INFO, so older pkg_resources
675 # can still parse it
676 metadata.write_pkg_info(cmd.egg_info)
677 finally:
678 metadata.name, metadata.version = oldname, oldver
679
680 safe = getattr(cmd.distribution, 'zip_safe', None)
681
682 bdist_egg.write_safety_flag(cmd.egg_info, safe)
683
684
685def warn_depends_obsolete(cmd, basename, filename):
686 """
687 Unused: left to avoid errors when updating (from source) from <= 67.8.
688 Old installations have a .dist-info directory with the entry-point
689 ``depends.txt = setuptools.command.egg_info:warn_depends_obsolete``.
690 This may trigger errors when running the first egg_info in build_meta.
691 TODO: Remove this function in a version sufficiently > 68.
692 """
693
694
695def _write_requirements(stream, reqs):
696 lines = yield_lines(reqs or ())
697
698 def append_cr(line):
699 return line + '\n'
700
701 lines = map(append_cr, lines)
702 stream.writelines(lines)
703
704
705def write_requirements(cmd, basename, filename):
706 dist = cmd.distribution
707 data = io.StringIO()
708 _write_requirements(data, dist.install_requires)
709 extras_require = dist.extras_require or {}
710 for extra in sorted(extras_require):
711 data.write('\n[{extra}]\n'.format(**vars()))
712 _write_requirements(data, extras_require[extra])
713 cmd.write_or_delete_file("requirements", filename, data.getvalue())
714
715
716def write_setup_requirements(cmd, basename, filename):
717 data = io.StringIO()
718 _write_requirements(data, cmd.distribution.setup_requires)
719 cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())
720
721
722def write_toplevel_names(cmd, basename, filename):
723 pkgs = dict.fromkeys(
724 [k.split('.', 1)[0] for k in cmd.distribution.iter_distribution_names()]
725 )
726 cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
727
728
729def overwrite_arg(cmd, basename, filename):
730 write_arg(cmd, basename, filename, True)
731
732
733def write_arg(cmd, basename, filename, force=False):
734 argname = os.path.splitext(basename)[0]
735 value = getattr(cmd.distribution, argname, None)
736 if value is not None:
737 value = '\n'.join(value) + '\n'
738 cmd.write_or_delete_file(argname, filename, value, force)
739
740
741def write_entries(cmd, basename, filename):
742 eps = _entry_points.load(cmd.distribution.entry_points)
743 defn = _entry_points.render(eps)
744 cmd.write_or_delete_file('entry points', filename, defn, True)
745
746
747def _egg_basename(egg_name, egg_version, py_version=None, platform=None):
748 """Compute filename of the output egg. Private API."""
749 name = _normalization.filename_component(egg_name)
750 version = _normalization.filename_component(egg_version)
751 egg = f"{name}-{version}-py{py_version or PY_MAJOR}"
752 if platform:
753 egg += f"-{platform}"
754 return egg
755
756
757class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
758 """Deprecated behavior warning for EggInfo, bypassing suppression."""