diff --git a/Lib/distutils/command/dist.py b/Lib/distutils/command/dist.py deleted file mode 100644 index ea61a4806e3..00000000000 --- a/Lib/distutils/command/dist.py +++ /dev/null @@ -1,558 +0,0 @@ -"""distutils.command.dist - -Implements the Distutils 'dist' command (create a source distribution).""" - -# created 1999/09/22, Greg Ward - -__rcsid__ = "$Id$" - -import sys, os, string, re -import fnmatch -from types import * -from glob import glob -from shutil import rmtree -from distutils.core import Command -from distutils.text_file import TextFile -from distutils.errors import DistutilsExecError - - -# Possible modes of operation: -# - require an explicit manifest that lists every single file (presumably -# along with a way to auto-generate the manifest) -# - require an explicit manifest, but allow it to have globs or -# filename patterns of some kind (and also have auto-generation) -# - allow an explict manifest, but automatically augment it at runtime -# with the source files mentioned in 'packages', 'py_modules', and -# 'ext_modules' (and any other such things that might come along) - -# I'm liking the third way. Possible gotchas: -# - redundant specification: 'packages' includes 'foo' and manifest -# includes 'foo/*.py' -# - obvious conflict: 'packages' includes 'foo' and manifest -# includes '! foo/*.py' (can't imagine why you'd want this) -# - subtle conflict: 'packages' includes 'foo' and manifest -# includes '! foo/bar.py' (this could well be desired: eg. exclude -# an experimental module from distribution) - -# Syntax for the manifest file: -# - if a line is just a Unix-style glob by itself, it's a "simple include -# pattern": go find all files that match and add them to the list -# of files -# - if a line is a glob preceded by "!", then it's a "simple exclude -# pattern": go over the current list of files and exclude any that -# match the glob pattern -# - if a line consists of a directory name followed by zero or more -# glob patterns, then we'll recursively explore that directory tree -# - the glob patterns can be include (no punctuation) or exclude -# (prefixed by "!", no space) -# - if no patterns given or the first pattern is not an include pattern, -# then assume "*" -- ie. find everything (and then start applying -# the rest of the patterns) -# - the patterns are given in order of increasing precedence, ie. -# the *last* one to match a given file applies to it -# -# example (ignoring auto-augmentation!): -# distutils/*.py -# distutils/command/*.py -# ! distutils/bleeding_edge.py -# examples/*.py -# examples/README -# -# smarter way (that *will* include distutils/command/bleeding_edge.py!) -# distutils *.py -# ! distutils/bleeding_edge.py -# examples !*~ !*.py[co] (same as: examples * !*~ !*.py[co]) -# test test_* *.txt !*~ !*.py[co] -# README -# setup.py -# -# The actual Distutils manifest (don't need to mention source files, -# README, setup.py -- they're automatically distributed!): -# examples !*~ !*.py[co] -# test !*~ !*.py[co] - -# The algorithm that will make it work: -# files = stuff from 'packages', 'py_modules', 'ext_modules', -# plus README, setup.py, ... ? -# foreach pattern in manifest file: -# if simple-include-pattern: # "distutils/*.py" -# files.append (glob (pattern)) -# elif simple-exclude-pattern: # "! distutils/foo*" -# xfiles = glob (pattern) -# remove all xfiles from files -# elif recursive-pattern: # "examples" (just a directory name) -# patterns = rest-of-words-on-line -# dir_files = list of all files under dir -# if patterns: -# if patterns[0] is an exclude-pattern: -# insert "*" at patterns[0] -# for file in dir_files: -# for dpattern in reverse (patterns): -# if file matches dpattern: -# if dpattern is an include-pattern: -# files.append (file) -# else: -# nothing, don't include it -# next file -# else: -# files.extend (dir_files) # ie. accept all of them - - -# Anyways, this is all implemented below -- BUT it is largely untested; I -# know it works for the simple case of distributing the Distutils, but -# haven't tried it on more complicated examples. Undoubtedly doing so will -# reveal bugs and cause delays, so I'm waiting until after I've released -# Distutils 0.1. - - -# Other things we need to look for in creating a source distribution: -# - make sure there's a README -# - make sure the distribution meta-info is supplied and non-empty -# (*must* have name, version, ((author and author_email) or -# (maintainer and maintainer_email)), url -# -# Frills: -# - make sure the setup script is called "setup.py" -# - make sure the README refers to "setup.py" (ie. has a line matching -# /^\s*python\s+setup\.py/) - -# A crazy idea that conflicts with having/requiring 'version' in setup.py: -# - make sure there's a version number in the "main file" (main file -# is __init__.py of first package, or the first module if no packages, -# or the first extension module if no pure Python modules) -# - XXX how do we look for __version__ in an extension module? -# - XXX do we import and look for __version__? or just scan source for -# /^__version__\s*=\s*"[^"]+"/ ? -# - what about 'version_from' as an alternative to 'version' -- then -# we know just where to search for the version -- no guessing about -# what the "main file" is - - - -class Dist (Command): - - description = "create a source distribution (tarball, zip file, etc.)" - - options = [('formats=', None, - "formats for source distribution (tar, ztar, gztar, or zip)"), - ('manifest=', 'm', - "name of manifest file"), - ('list-only', 'l', - "just list files that would be distributed"), - ('keep-tree', 'k', - "keep the distribution tree around after creating " + - "archive file(s)"), - ] - - default_format = { 'posix': 'gztar', - 'nt': 'zip' } - - exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines - - - def set_default_options (self): - self.formats = None - self.manifest = None - self.list_only = 0 - self.keep_tree = 0 - - - def set_final_options (self): - if self.formats is None: - try: - self.formats = [self.default_format[os.name]] - except KeyError: - raise DistutilsPlatformError, \ - "don't know how to build source distributions on " + \ - "%s platform" % os.name - elif type (self.formats) is StringType: - self.formats = string.split (self.formats, ',') - - if self.manifest is None: - self.manifest = "MANIFEST" - - - def run (self): - - self.check_metadata () - - self.files = [] - self.find_defaults () - self.read_manifest () - - if self.list_only: - for f in self.files: - print f - - else: - self.make_distribution () - - - def check_metadata (self): - - dist = self.distribution - - missing = [] - for attr in ('name', 'version', 'url'): - if not (hasattr (dist, attr) and getattr (dist, attr)): - missing.append (attr) - - if missing: - self.warn ("missing required meta-data: " + - string.join (missing, ", ")) - - if dist.author: - if not dist.author_email: - self.warn ("missing meta-data: if 'author' supplied, " + - "'author_email' must be supplied too") - elif dist.maintainer: - if not dist.maintainer_email: - self.warn ("missing meta-data: if 'maintainer' supplied, " + - "'maintainer_email' must be supplied too") - else: - self.warn ("missing meta-data: either (author and author_email) " + - "or (maintainer and maintainer_email) " + - "must be supplied") - - # check_metadata () - - - def find_defaults (self): - - standards = [('README', 'README.txt'), 'setup.py'] - for fn in standards: - if type (fn) is TupleType: - alts = fn - for fn in alts: - if os.path.exists (fn): - got_it = 1 - self.files.append (fn) - break - - if not got_it: - self.warn ("standard file not found: should have one of " + - string.join (alts, ', ')) - else: - if os.path.exists (fn): - self.files.append (fn) - else: - self.warn ("standard file %s not found" % fn) - - optional = ['test/test*.py'] - for pattern in optional: - files = filter (os.path.isfile, glob (pattern)) - if files: - self.files.extend (files) - - if self.distribution.packages or self.distribution.py_modules: - build_py = self.find_peer ('build_py') - build_py.ensure_ready () - self.files.extend (build_py.get_source_files ()) - - if self.distribution.ext_modules: - build_ext = self.find_peer ('build_ext') - build_ext.ensure_ready () - self.files.extend (build_ext.get_source_files ()) - - - - def open_manifest (self, filename): - return TextFile (filename, - strip_comments=1, - skip_blanks=1, - join_lines=1, - lstrip_ws=1, - rstrip_ws=1, - collapse_ws=1) - - - def search_dir (self, dir, patterns): - - allfiles = findall (dir) - if patterns: - if patterns[0][0] == "!": # starts with an exclude spec? - patterns.insert (0, "*")# then accept anything that isn't - # explicitly excluded - - act_patterns = [] # "action-patterns": (include,regexp) - # tuples where include is a boolean - for pattern in patterns: - if pattern[0] == '!': - act_patterns.append \ - ((0, re.compile (fnmatch.translate (pattern[1:])))) - else: - act_patterns.append \ - ((1, re.compile (fnmatch.translate (pattern)))) - act_patterns.reverse() - - - files = [] - for file in allfiles: - for (include,regexp) in act_patterns: - if regexp.search (file): - if include: - files.append (file) - break # continue to next file - else: - files = allfiles - - return files - - # search_dir () - - - def exclude_files (self, pattern): - - regexp = re.compile (fnmatch.translate (pattern)) - for i in range (len (self.files)-1, -1, -1): - if regexp.search (self.files[i]): - del self.files[i] - - - def read_manifest (self): - - # self.files had better already be defined (and hold the - # "automatically found" files -- Python modules and extensions, - # README, setup script, ...) - assert self.files is not None - - try: - manifest = self.open_manifest (self.manifest) - except IOError, exc: - if type (exc) is InstanceType and hasattr (exc, 'strerror'): - msg = "could not open MANIFEST (%s)" % \ - string.lower (exc.strerror) - else: - msg = "could not open MANIFST" - - self.warn (msg + ": using default file list") - return - - while 1: - - pattern = manifest.readline() - if pattern is None: # end of file - break - - # Cases: - # 1) simple-include: "*.py", "foo/*.py", "doc/*.html", "FAQ" - # 2) simple-exclude: same, prefaced by ! - # 3) recursive: multi-word line, first word a directory - - exclude = self.exclude_re.match (pattern) - if exclude: - pattern = exclude.group (1) - - words = string.split (pattern) - assert words # must have something! - if os.name != 'posix': - words[0] = apply (os.path.join, string.split (words[0], '/')) - - # First word is a directory, possibly with include/exclude - # patterns making up the rest of the line: it's a recursive - # pattern - if os.path.isdir (words[0]): - if exclude: - manifest.warn ("exclude (!) doesn't apply to " + - "whole directory trees") - continue - - dir_files = self.search_dir (words[0], words[1:]) - self.files.extend (dir_files) - - # Multiple words in pattern: that's a no-no unless the first - # word is a directory name - elif len (words) > 1: - manifest.warn ("can't have multiple words unless first word " + - "('%s') is a directory name" % words[0]) - continue - - # Single word, no bang: it's a "simple include pattern" - elif not exclude: - matches = filter (os.path.isfile, glob (pattern)) - if matches: - self.files.extend (matches) - else: - manifest.warn ("no matches for '%s' found" % pattern) - - - # Single word prefixed with a bang: it's a "simple exclude pattern" - else: - if self.exclude_files (pattern) == 0: - manifest.warn ("no files excluded by '%s'" % pattern) - - # if/elif/.../else on 'pattern' - - # loop over lines of 'manifest' - - # read_manifest () - - - def make_release_tree (self, base_dir, files): - - # XXX this is Unix-specific - - # First get the list of directories to create - need_dir = {} - for file in files: - need_dir[os.path.join (base_dir, os.path.dirname (file))] = 1 - need_dirs = need_dir.keys() - need_dirs.sort() - - # Now create them - for dir in need_dirs: - self.mkpath (dir) - - # And walk over the list of files, either making a hard link (if - # os.link exists) to each one that doesn't already exist in its - # corresponding location under 'base_dir', or copying each file - # that's out-of-date in 'base_dir'. (Usually, all files will be - # out-of-date, because by default we blow away 'base_dir' when - # we're done making the distribution archives.) - - try: - link = os.link - msg = "making hard links in %s..." % base_dir - except AttributeError: - link = 0 - msg = "copying files to %s..." % base_dir - - self.announce (msg) - for file in files: - dest = os.path.join (base_dir, file) - if link: - if not os.path.exists (dest): - self.execute (os.link, (file, dest), - "linking %s -> %s" % (file, dest)) - else: - self.copy_file (file, dest) - - # make_release_tree () - - - def nuke_release_tree (self, base_dir): - try: - self.execute (rmtree, (base_dir,), - "removing %s" % base_dir) - except (IOError, OSError), exc: - if exc.filename: - msg = "error removing %s: %s (%s)" % \ - (base_dir, exc.strerror, exc.filename) - else: - msg = "error removing %s: %s" % (base_dir, exc.strerror) - self.warn (msg) - - - def make_tarball (self, base_dir, compress="gzip"): - - # XXX GNU tar 1.13 has a nifty option to add a prefix directory. - # It's pretty new, though, so we certainly can't require it -- - # but it would be nice to take advantage of it to skip the - # "create a tree of hardlinks" step! (Would also be nice to - # detect GNU tar to use its 'z' option and save a step.) - - if compress is not None and compress not in ('gzip', 'compress'): - raise ValueError, \ - "if given, 'compress' must be 'gzip' or 'compress'" - - archive_name = base_dir + ".tar" - self.spawn (["tar", "-cf", archive_name, base_dir]) - - if compress: - self.spawn ([compress, archive_name]) - - - def make_zipfile (self, base_dir): - - # This initially assumed the Unix 'zip' utility -- but - # apparently InfoZIP's zip.exe works the same under Windows, so - # no changes needed! - - try: - self.spawn (["zip", "-r", base_dir + ".zip", base_dir]) - except DistutilsExecError: - - # XXX really should distinguish between "couldn't find - # external 'zip' command" and "zip failed" -- shouldn't try - # again in the latter case. (I think fixing this will - # require some cooperation from the spawn module -- perhaps - # a utility function to search the path, so we can fallback - # on zipfile.py without the failed spawn.) - try: - import zipfile - except ImportError: - raise DistutilsExecError, \ - ("unable to create zip file '%s.zip': " + - "could neither find a standalone zip utility nor " + - "import the 'zipfile' module") % base_dir - - z = zipfile.ZipFile (base_dir + ".zip", "wb", - compression=zipfile.ZIP_DEFLATED) - - def visit (z, dirname, names): - for name in names: - path = os.path.join (dirname, name) - if os.path.isfile (path): - z.write (path, path) - - os.path.walk (base_dir, visit, z) - z.close() - - - def make_distribution (self): - - # Don't warn about missing meta-data here -- should be done - # elsewhere. - name = self.distribution.name or "UNKNOWN" - version = self.distribution.version - - if version: - base_dir = "%s-%s" % (name, version) - else: - base_dir = name - - # Remove any files that match "base_dir" from the fileset -- we - # don't want to go distributing the distribution inside itself! - self.exclude_files (base_dir + "*") - - self.make_release_tree (base_dir, self.files) - for fmt in self.formats: - if fmt == 'gztar': - self.make_tarball (base_dir, compress='gzip') - elif fmt == 'ztar': - self.make_tarball (base_dir, compress='compress') - elif fmt == 'tar': - self.make_tarball (base_dir, compress=None) - elif fmt == 'zip': - self.make_zipfile (base_dir) - - if not self.keep_tree: - self.nuke_release_tree (base_dir) - -# class Dist - - -# ---------------------------------------------------------------------- -# Utility functions - -def findall (dir = os.curdir): - """Find all files under 'dir' and return the sorted list of full - filenames (relative to 'dir').""" - - list = [] - stack = [dir] - pop = stack.pop - push = stack.append - - while stack: - dir = pop() - names = os.listdir (dir) - - for name in names: - fullname = os.path.join (dir, name) - list.append (fullname) - if os.path.isdir (fullname) and not os.path.islink(fullname): - push (fullname) - - list.sort() - return list