OLD | NEW |
(Empty) | |
| 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.util import convert_path |
| 7 from distutils import log |
| 8 import distutils.errors |
| 9 import distutils.filelist |
| 10 import os |
| 11 import re |
| 12 import sys |
| 13 |
| 14 try: |
| 15 import packaging.version |
| 16 except ImportError: |
| 17 # fallback to vendored version |
| 18 import setuptools._vendor.packaging.version |
| 19 packaging = setuptools._vendor.packaging |
| 20 |
| 21 from setuptools import Command |
| 22 from setuptools.command.sdist import sdist |
| 23 from setuptools.compat import basestring, PY3, StringIO |
| 24 from setuptools import svn_utils |
| 25 from setuptools.command.sdist import walk_revctrl |
| 26 from pkg_resources import ( |
| 27 parse_requirements, safe_name, parse_version, |
| 28 safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) |
| 29 import setuptools.unicode_utils as unicode_utils |
| 30 |
| 31 |
| 32 class egg_info(Command): |
| 33 description = "create a distribution's .egg-info directory" |
| 34 |
| 35 user_options = [ |
| 36 ('egg-base=', 'e', "directory containing .egg-info directories" |
| 37 " (default: top of the source tree)"), |
| 38 ('tag-svn-revision', 'r', |
| 39 "Add subversion revision ID to version number"), |
| 40 ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), |
| 41 ('tag-build=', 'b', "Specify explicit tag to add to version number"), |
| 42 ('no-svn-revision', 'R', |
| 43 "Don't add subversion revision ID [default]"), |
| 44 ('no-date', 'D', "Don't include date stamp [default]"), |
| 45 ] |
| 46 |
| 47 boolean_options = ['tag-date', 'tag-svn-revision'] |
| 48 negative_opt = {'no-svn-revision': 'tag-svn-revision', |
| 49 'no-date': 'tag-date'} |
| 50 |
| 51 def initialize_options(self): |
| 52 self.egg_name = None |
| 53 self.egg_version = None |
| 54 self.egg_base = None |
| 55 self.egg_info = None |
| 56 self.tag_build = None |
| 57 self.tag_svn_revision = 0 |
| 58 self.tag_date = 0 |
| 59 self.broken_egg_info = False |
| 60 self.vtags = None |
| 61 |
| 62 def save_version_info(self, filename): |
| 63 from setuptools.command.setopt import edit_config |
| 64 |
| 65 values = dict( |
| 66 egg_info=dict( |
| 67 tag_svn_revision=0, |
| 68 tag_date=0, |
| 69 tag_build=self.tags(), |
| 70 ) |
| 71 ) |
| 72 edit_config(filename, values) |
| 73 |
| 74 def finalize_options(self): |
| 75 self.egg_name = safe_name(self.distribution.get_name()) |
| 76 self.vtags = self.tags() |
| 77 self.egg_version = self.tagged_version() |
| 78 |
| 79 parsed_version = parse_version(self.egg_version) |
| 80 |
| 81 try: |
| 82 is_version = isinstance(parsed_version, packaging.version.Version) |
| 83 spec = ( |
| 84 "%s==%s" if is_version else "%s===%s" |
| 85 ) |
| 86 list( |
| 87 parse_requirements(spec % (self.egg_name, self.egg_version)) |
| 88 ) |
| 89 except ValueError: |
| 90 raise distutils.errors.DistutilsOptionError( |
| 91 "Invalid distribution name or version syntax: %s-%s" % |
| 92 (self.egg_name, self.egg_version) |
| 93 ) |
| 94 |
| 95 if self.egg_base is None: |
| 96 dirs = self.distribution.package_dir |
| 97 self.egg_base = (dirs or {}).get('', os.curdir) |
| 98 |
| 99 self.ensure_dirname('egg_base') |
| 100 self.egg_info = to_filename(self.egg_name) + '.egg-info' |
| 101 if self.egg_base != os.curdir: |
| 102 self.egg_info = os.path.join(self.egg_base, self.egg_info) |
| 103 if '-' in self.egg_name: |
| 104 self.check_broken_egg_info() |
| 105 |
| 106 # Set package version for the benefit of dumber commands |
| 107 # (e.g. sdist, bdist_wininst, etc.) |
| 108 # |
| 109 self.distribution.metadata.version = self.egg_version |
| 110 |
| 111 # If we bootstrapped around the lack of a PKG-INFO, as might be the |
| 112 # case in a fresh checkout, make sure that any special tags get added |
| 113 # to the version info |
| 114 # |
| 115 pd = self.distribution._patched_dist |
| 116 if pd is not None and pd.key == self.egg_name.lower(): |
| 117 pd._version = self.egg_version |
| 118 pd._parsed_version = parse_version(self.egg_version) |
| 119 self.distribution._patched_dist = None |
| 120 |
| 121 def write_or_delete_file(self, what, filename, data, force=False): |
| 122 """Write `data` to `filename` or delete if empty |
| 123 |
| 124 If `data` is non-empty, this routine is the same as ``write_file()``. |
| 125 If `data` is empty but not ``None``, this is the same as calling |
| 126 ``delete_file(filename)`. If `data` is ``None``, then this is a no-op |
| 127 unless `filename` exists, in which case a warning is issued about the |
| 128 orphaned file (if `force` is false), or deleted (if `force` is true). |
| 129 """ |
| 130 if data: |
| 131 self.write_file(what, filename, data) |
| 132 elif os.path.exists(filename): |
| 133 if data is None and not force: |
| 134 log.warn( |
| 135 "%s not set in setup(), but %s exists", what, filename |
| 136 ) |
| 137 return |
| 138 else: |
| 139 self.delete_file(filename) |
| 140 |
| 141 def write_file(self, what, filename, data): |
| 142 """Write `data` to `filename` (if not a dry run) after announcing it |
| 143 |
| 144 `what` is used in a log message to identify what is being written |
| 145 to the file. |
| 146 """ |
| 147 log.info("writing %s to %s", what, filename) |
| 148 if PY3: |
| 149 data = data.encode("utf-8") |
| 150 if not self.dry_run: |
| 151 f = open(filename, 'wb') |
| 152 f.write(data) |
| 153 f.close() |
| 154 |
| 155 def delete_file(self, filename): |
| 156 """Delete `filename` (if not a dry run) after announcing it""" |
| 157 log.info("deleting %s", filename) |
| 158 if not self.dry_run: |
| 159 os.unlink(filename) |
| 160 |
| 161 def tagged_version(self): |
| 162 version = self.distribution.get_version() |
| 163 # egg_info may be called more than once for a distribution, |
| 164 # in which case the version string already contains all tags. |
| 165 if self.vtags and version.endswith(self.vtags): |
| 166 return safe_version(version) |
| 167 return safe_version(version + self.vtags) |
| 168 |
| 169 def run(self): |
| 170 self.mkpath(self.egg_info) |
| 171 installer = self.distribution.fetch_build_egg |
| 172 for ep in iter_entry_points('egg_info.writers'): |
| 173 writer = ep.load(installer=installer) |
| 174 writer(self, ep.name, os.path.join(self.egg_info, ep.name)) |
| 175 |
| 176 # Get rid of native_libs.txt if it was put there by older bdist_egg |
| 177 nl = os.path.join(self.egg_info, "native_libs.txt") |
| 178 if os.path.exists(nl): |
| 179 self.delete_file(nl) |
| 180 |
| 181 self.find_sources() |
| 182 |
| 183 def tags(self): |
| 184 version = '' |
| 185 if self.tag_build: |
| 186 version += self.tag_build |
| 187 if self.tag_svn_revision: |
| 188 rev = self.get_svn_revision() |
| 189 if rev: # is 0 if it's not an svn working copy |
| 190 version += '-r%s' % rev |
| 191 if self.tag_date: |
| 192 import time |
| 193 |
| 194 version += time.strftime("-%Y%m%d") |
| 195 return version |
| 196 |
| 197 @staticmethod |
| 198 def get_svn_revision(): |
| 199 return str(svn_utils.SvnInfo.load(os.curdir).get_revision()) |
| 200 |
| 201 def find_sources(self): |
| 202 """Generate SOURCES.txt manifest file""" |
| 203 manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") |
| 204 mm = manifest_maker(self.distribution) |
| 205 mm.manifest = manifest_filename |
| 206 mm.run() |
| 207 self.filelist = mm.filelist |
| 208 |
| 209 def check_broken_egg_info(self): |
| 210 bei = self.egg_name + '.egg-info' |
| 211 if self.egg_base != os.curdir: |
| 212 bei = os.path.join(self.egg_base, bei) |
| 213 if os.path.exists(bei): |
| 214 log.warn( |
| 215 "-" * 78 + '\n' |
| 216 "Note: Your current .egg-info directory has a '-' in its name;" |
| 217 '\nthis will not work correctly with "setup.py develop".\n\n' |
| 218 'Please rename %s to %s to correct this problem.\n' + '-' * 78, |
| 219 bei, self.egg_info |
| 220 ) |
| 221 self.broken_egg_info = self.egg_info |
| 222 self.egg_info = bei # make it work for now |
| 223 |
| 224 |
| 225 class FileList(_FileList): |
| 226 """File list that accepts only existing, platform-independent paths""" |
| 227 |
| 228 def append(self, item): |
| 229 if item.endswith('\r'): # Fix older sdists built on Windows |
| 230 item = item[:-1] |
| 231 path = convert_path(item) |
| 232 |
| 233 if self._safe_path(path): |
| 234 self.files.append(path) |
| 235 |
| 236 def extend(self, paths): |
| 237 self.files.extend(filter(self._safe_path, paths)) |
| 238 |
| 239 def _repair(self): |
| 240 """ |
| 241 Replace self.files with only safe paths |
| 242 |
| 243 Because some owners of FileList manipulate the underlying |
| 244 ``files`` attribute directly, this method must be called to |
| 245 repair those paths. |
| 246 """ |
| 247 self.files = list(filter(self._safe_path, self.files)) |
| 248 |
| 249 def _safe_path(self, path): |
| 250 enc_warn = "'%s' not %s encodable -- skipping" |
| 251 |
| 252 # To avoid accidental trans-codings errors, first to unicode |
| 253 u_path = unicode_utils.filesys_decode(path) |
| 254 if u_path is None: |
| 255 log.warn("'%s' in unexpected encoding -- skipping" % path) |
| 256 return False |
| 257 |
| 258 # Must ensure utf-8 encodability |
| 259 utf8_path = unicode_utils.try_encode(u_path, "utf-8") |
| 260 if utf8_path is None: |
| 261 log.warn(enc_warn, path, 'utf-8') |
| 262 return False |
| 263 |
| 264 try: |
| 265 # accept is either way checks out |
| 266 if os.path.exists(u_path) or os.path.exists(utf8_path): |
| 267 return True |
| 268 # this will catch any encode errors decoding u_path |
| 269 except UnicodeEncodeError: |
| 270 log.warn(enc_warn, path, sys.getfilesystemencoding()) |
| 271 |
| 272 |
| 273 class manifest_maker(sdist): |
| 274 template = "MANIFEST.in" |
| 275 |
| 276 def initialize_options(self): |
| 277 self.use_defaults = 1 |
| 278 self.prune = 1 |
| 279 self.manifest_only = 1 |
| 280 self.force_manifest = 1 |
| 281 |
| 282 def finalize_options(self): |
| 283 pass |
| 284 |
| 285 def run(self): |
| 286 self.filelist = FileList() |
| 287 if not os.path.exists(self.manifest): |
| 288 self.write_manifest() # it must exist so it'll get in the list |
| 289 self.filelist.findall() |
| 290 self.add_defaults() |
| 291 if os.path.exists(self.template): |
| 292 self.read_template() |
| 293 self.prune_file_list() |
| 294 self.filelist.sort() |
| 295 self.filelist.remove_duplicates() |
| 296 self.write_manifest() |
| 297 |
| 298 def _manifest_normalize(self, path): |
| 299 path = unicode_utils.filesys_decode(path) |
| 300 return path.replace(os.sep, '/') |
| 301 |
| 302 def write_manifest(self): |
| 303 """ |
| 304 Write the file list in 'self.filelist' to the manifest file |
| 305 named by 'self.manifest'. |
| 306 """ |
| 307 self.filelist._repair() |
| 308 |
| 309 # Now _repairs should encodability, but not unicode |
| 310 files = [self._manifest_normalize(f) for f in self.filelist.files] |
| 311 msg = "writing manifest file '%s'" % self.manifest |
| 312 self.execute(write_file, (self.manifest, files), msg) |
| 313 |
| 314 def warn(self, msg): # suppress missing-file warnings from sdist |
| 315 if not msg.startswith("standard file not found:"): |
| 316 sdist.warn(self, msg) |
| 317 |
| 318 def add_defaults(self): |
| 319 sdist.add_defaults(self) |
| 320 self.filelist.append(self.template) |
| 321 self.filelist.append(self.manifest) |
| 322 rcfiles = list(walk_revctrl()) |
| 323 if rcfiles: |
| 324 self.filelist.extend(rcfiles) |
| 325 elif os.path.exists(self.manifest): |
| 326 self.read_manifest() |
| 327 ei_cmd = self.get_finalized_command('egg_info') |
| 328 self._add_egg_info(cmd=ei_cmd) |
| 329 self.filelist.include_pattern("*", prefix=ei_cmd.egg_info) |
| 330 |
| 331 def _add_egg_info(self, cmd): |
| 332 """ |
| 333 Add paths for egg-info files for an external egg-base. |
| 334 |
| 335 The egg-info files are written to egg-base. If egg-base is |
| 336 outside the current working directory, this method |
| 337 searchs the egg-base directory for files to include |
| 338 in the manifest. Uses distutils.filelist.findall (which is |
| 339 really the version monkeypatched in by setuptools/__init__.py) |
| 340 to perform the search. |
| 341 |
| 342 Since findall records relative paths, prefix the returned |
| 343 paths with cmd.egg_base, so add_default's include_pattern call |
| 344 (which is looking for the absolute cmd.egg_info) will match |
| 345 them. |
| 346 """ |
| 347 if cmd.egg_base == os.curdir: |
| 348 # egg-info files were already added by something else |
| 349 return |
| 350 |
| 351 discovered = distutils.filelist.findall(cmd.egg_base) |
| 352 resolved = (os.path.join(cmd.egg_base, path) for path in discovered) |
| 353 self.filelist.allfiles.extend(resolved) |
| 354 |
| 355 def prune_file_list(self): |
| 356 build = self.get_finalized_command('build') |
| 357 base_dir = self.distribution.get_fullname() |
| 358 self.filelist.exclude_pattern(None, prefix=build.build_base) |
| 359 self.filelist.exclude_pattern(None, prefix=base_dir) |
| 360 sep = re.escape(os.sep) |
| 361 self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, |
| 362 is_regex=1) |
| 363 |
| 364 |
| 365 def write_file(filename, contents): |
| 366 """Create a file with the specified name and write 'contents' (a |
| 367 sequence of strings without line terminators) to it. |
| 368 """ |
| 369 contents = "\n".join(contents) |
| 370 |
| 371 # assuming the contents has been vetted for utf-8 encoding |
| 372 contents = contents.encode("utf-8") |
| 373 |
| 374 with open(filename, "wb") as f: # always write POSIX-style manifest |
| 375 f.write(contents) |
| 376 |
| 377 |
| 378 def write_pkg_info(cmd, basename, filename): |
| 379 log.info("writing %s", filename) |
| 380 if not cmd.dry_run: |
| 381 metadata = cmd.distribution.metadata |
| 382 metadata.version, oldver = cmd.egg_version, metadata.version |
| 383 metadata.name, oldname = cmd.egg_name, metadata.name |
| 384 try: |
| 385 # write unescaped data to PKG-INFO, so older pkg_resources |
| 386 # can still parse it |
| 387 metadata.write_pkg_info(cmd.egg_info) |
| 388 finally: |
| 389 metadata.name, metadata.version = oldname, oldver |
| 390 |
| 391 safe = getattr(cmd.distribution, 'zip_safe', None) |
| 392 from setuptools.command import bdist_egg |
| 393 |
| 394 bdist_egg.write_safety_flag(cmd.egg_info, safe) |
| 395 |
| 396 |
| 397 def warn_depends_obsolete(cmd, basename, filename): |
| 398 if os.path.exists(filename): |
| 399 log.warn( |
| 400 "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" |
| 401 "Use the install_requires/extras_require setup() args instead." |
| 402 ) |
| 403 |
| 404 |
| 405 def _write_requirements(stream, reqs): |
| 406 lines = yield_lines(reqs or ()) |
| 407 append_cr = lambda line: line + '\n' |
| 408 lines = map(append_cr, lines) |
| 409 stream.writelines(lines) |
| 410 |
| 411 |
| 412 def write_requirements(cmd, basename, filename): |
| 413 dist = cmd.distribution |
| 414 data = StringIO() |
| 415 _write_requirements(data, dist.install_requires) |
| 416 extras_require = dist.extras_require or {} |
| 417 for extra in sorted(extras_require): |
| 418 data.write('\n[{extra}]\n'.format(**vars())) |
| 419 _write_requirements(data, extras_require[extra]) |
| 420 cmd.write_or_delete_file("requirements", filename, data.getvalue()) |
| 421 |
| 422 |
| 423 def write_toplevel_names(cmd, basename, filename): |
| 424 pkgs = dict.fromkeys( |
| 425 [ |
| 426 k.split('.', 1)[0] |
| 427 for k in cmd.distribution.iter_distribution_names() |
| 428 ] |
| 429 ) |
| 430 cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') |
| 431 |
| 432 |
| 433 def overwrite_arg(cmd, basename, filename): |
| 434 write_arg(cmd, basename, filename, True) |
| 435 |
| 436 |
| 437 def write_arg(cmd, basename, filename, force=False): |
| 438 argname = os.path.splitext(basename)[0] |
| 439 value = getattr(cmd.distribution, argname, None) |
| 440 if value is not None: |
| 441 value = '\n'.join(value) + '\n' |
| 442 cmd.write_or_delete_file(argname, filename, value, force) |
| 443 |
| 444 |
| 445 def write_entries(cmd, basename, filename): |
| 446 ep = cmd.distribution.entry_points |
| 447 |
| 448 if isinstance(ep, basestring) or ep is None: |
| 449 data = ep |
| 450 elif ep is not None: |
| 451 data = [] |
| 452 for section, contents in sorted(ep.items()): |
| 453 if not isinstance(contents, basestring): |
| 454 contents = EntryPoint.parse_group(section, contents) |
| 455 contents = '\n'.join(sorted(map(str, contents.values()))) |
| 456 data.append('[%s]\n%s\n\n' % (section, contents)) |
| 457 data = ''.join(data) |
| 458 |
| 459 cmd.write_or_delete_file('entry points', filename, data, True) |
| 460 |
| 461 |
| 462 def get_pkg_info_revision(): |
| 463 # See if we can get a -r### off of PKG-INFO, in case this is an sdist of |
| 464 # a subversion revision |
| 465 # |
| 466 if os.path.exists('PKG-INFO'): |
| 467 f = open('PKG-INFO', 'rU') |
| 468 for line in f: |
| 469 match = re.match(r"Version:.*-r(\d+)\s*$", line) |
| 470 if match: |
| 471 return int(match.group(1)) |
| 472 f.close() |
| 473 return 0 |
OLD | NEW |