comparison lib/python3.8/site-packages/setuptools/command/egg_info.py @ 0:9e54283cc701 draft

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