summaryrefslogtreecommitdiffstats
path: root/Lib/distutils/command
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>2000-02-17 23:54:55 (GMT)
committerGreg Ward <gward@python.net>2000-02-17 23:54:55 (GMT)
commit3d6b023f5cf7801a6db44cd943f810fa595730fe (patch)
tree57ed5d2296eb1b05e550adc55062b989cfde5d6b /Lib/distutils/command
parentefd0694a2dc5f18b1205a0535949f4fb42fa3745 (diff)
downloadcpython-3d6b023f5cf7801a6db44cd943f810fa595730fe.zip
cpython-3d6b023f5cf7801a6db44cd943f810fa595730fe.tar.gz
cpython-3d6b023f5cf7801a6db44cd943f810fa595730fe.tar.bz2
The 'dist' command is dead -- long live the 'sdist' command!
Diffstat (limited to 'Lib/distutils/command')
-rw-r--r--Lib/distutils/command/dist.py558
1 files changed, 0 insertions, 558 deletions
diff --git a/Lib/distutils/command/dist.py b/Lib/distutils/command/dist.py
deleted file mode 100644
index ea61a48..0000000
--- 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