Home | History | Annotate | Download | only in command
      1 """distutils.command.sdist
      2 
      3 Implements the Distutils 'sdist' command (create a source distribution)."""
      4 
      5 __revision__ = "$Id$"
      6 
      7 import os
      8 import string
      9 import sys
     10 from glob import glob
     11 from warnings import warn
     12 
     13 from distutils.core import Command
     14 from distutils import dir_util, dep_util, file_util, archive_util
     15 from distutils.text_file import TextFile
     16 from distutils.errors import (DistutilsPlatformError, DistutilsOptionError,
     17                               DistutilsTemplateError)
     18 from distutils.filelist import FileList
     19 from distutils import log
     20 from distutils.util import convert_path
     21 
     22 def show_formats():
     23     """Print all possible values for the 'formats' option (used by
     24     the "--help-formats" command-line option).
     25     """
     26     from distutils.fancy_getopt import FancyGetopt
     27     from distutils.archive_util import ARCHIVE_FORMATS
     28     formats = []
     29     for format in ARCHIVE_FORMATS.keys():
     30         formats.append(("formats=" + format, None,
     31                         ARCHIVE_FORMATS[format][2]))
     32     formats.sort()
     33     FancyGetopt(formats).print_help(
     34         "List of available source distribution formats:")
     35 
     36 class sdist(Command):
     37 
     38     description = "create a source distribution (tarball, zip file, etc.)"
     39 
     40     def checking_metadata(self):
     41         """Callable used for the check sub-command.
     42 
     43         Placed here so user_options can view it"""
     44         return self.metadata_check
     45 
     46     user_options = [
     47         ('template=', 't',
     48          "name of manifest template file [default: MANIFEST.in]"),
     49         ('manifest=', 'm',
     50          "name of manifest file [default: MANIFEST]"),
     51         ('use-defaults', None,
     52          "include the default file set in the manifest "
     53          "[default; disable with --no-defaults]"),
     54         ('no-defaults', None,
     55          "don't include the default file set"),
     56         ('prune', None,
     57          "specifically exclude files/directories that should not be "
     58          "distributed (build tree, RCS/CVS dirs, etc.) "
     59          "[default; disable with --no-prune]"),
     60         ('no-prune', None,
     61          "don't automatically exclude anything"),
     62         ('manifest-only', 'o',
     63          "just regenerate the manifest and then stop "
     64          "(implies --force-manifest)"),
     65         ('force-manifest', 'f',
     66          "forcibly regenerate the manifest and carry on as usual. "
     67          "Deprecated: now the manifest is always regenerated."),
     68         ('formats=', None,
     69          "formats for source distribution (comma-separated list)"),
     70         ('keep-temp', 'k',
     71          "keep the distribution tree around after creating " +
     72          "archive file(s)"),
     73         ('dist-dir=', 'd',
     74          "directory to put the source distribution archive(s) in "
     75          "[default: dist]"),
     76         ('metadata-check', None,
     77          "Ensure that all required elements of meta-data "
     78          "are supplied. Warn if any missing. [default]"),
     79         ('owner=', 'u',
     80          "Owner name used when creating a tar file [default: current user]"),
     81         ('group=', 'g',
     82          "Group name used when creating a tar file [default: current group]"),
     83         ]
     84 
     85     boolean_options = ['use-defaults', 'prune',
     86                        'manifest-only', 'force-manifest',
     87                        'keep-temp', 'metadata-check']
     88 
     89     help_options = [
     90         ('help-formats', None,
     91          "list available distribution formats", show_formats),
     92         ]
     93 
     94     negative_opt = {'no-defaults': 'use-defaults',
     95                     'no-prune': 'prune' }
     96 
     97     default_format = {'posix': 'gztar',
     98                       'nt': 'zip' }
     99 
    100     sub_commands = [('check', checking_metadata)]
    101 
    102     def initialize_options(self):
    103         # 'template' and 'manifest' are, respectively, the names of

    104         # the manifest template and manifest file.

    105         self.template = None
    106         self.manifest = None
    107 
    108         # 'use_defaults': if true, we will include the default file set

    109         # in the manifest

    110         self.use_defaults = 1
    111         self.prune = 1
    112 
    113         self.manifest_only = 0
    114         self.force_manifest = 0
    115 
    116         self.formats = None
    117         self.keep_temp = 0
    118         self.dist_dir = None
    119 
    120         self.archive_files = None
    121         self.metadata_check = 1
    122         self.owner = None
    123         self.group = None
    124 
    125     def finalize_options(self):
    126         if self.manifest is None:
    127             self.manifest = "MANIFEST"
    128         if self.template is None:
    129             self.template = "MANIFEST.in"
    130 
    131         self.ensure_string_list('formats')
    132         if self.formats is None:
    133             try:
    134                 self.formats = [self.default_format[os.name]]
    135             except KeyError:
    136                 raise DistutilsPlatformError, \
    137                       "don't know how to create source distributions " + \
    138                       "on platform %s" % os.name
    139 
    140         bad_format = archive_util.check_archive_formats(self.formats)
    141         if bad_format:
    142             raise DistutilsOptionError, \
    143                   "unknown archive format '%s'" % bad_format
    144 
    145         if self.dist_dir is None:
    146             self.dist_dir = "dist"
    147 
    148     def run(self):
    149         # 'filelist' contains the list of files that will make up the

    150         # manifest

    151         self.filelist = FileList()
    152 
    153         # Run sub commands

    154         for cmd_name in self.get_sub_commands():
    155             self.run_command(cmd_name)
    156 
    157         # Do whatever it takes to get the list of files to process

    158         # (process the manifest template, read an existing manifest,

    159         # whatever).  File list is accumulated in 'self.filelist'.

    160         self.get_file_list()
    161 
    162         # If user just wanted us to regenerate the manifest, stop now.

    163         if self.manifest_only:
    164             return
    165 
    166         # Otherwise, go ahead and create the source distribution tarball,

    167         # or zipfile, or whatever.

    168         self.make_distribution()
    169 
    170     def check_metadata(self):
    171         """Deprecated API."""
    172         warn("distutils.command.sdist.check_metadata is deprecated, \
    173               use the check command instead", PendingDeprecationWarning)
    174         check = self.distribution.get_command_obj('check')
    175         check.ensure_finalized()
    176         check.run()
    177 
    178     def get_file_list(self):
    179         """Figure out the list of files to include in the source
    180         distribution, and put it in 'self.filelist'.  This might involve
    181         reading the manifest template (and writing the manifest), or just
    182         reading the manifest, or just using the default file set -- it all
    183         depends on the user's options.
    184         """
    185         # new behavior:

    186         # the file list is recalculated everytime because

    187         # even if MANIFEST.in or setup.py are not changed

    188         # the user might have added some files in the tree that

    189         # need to be included.

    190         #

    191         #  This makes --force the default and only behavior.

    192         template_exists = os.path.isfile(self.template)
    193         if not template_exists:
    194             self.warn(("manifest template '%s' does not exist " +
    195                         "(using default file list)") %
    196                         self.template)
    197         self.filelist.findall()
    198 
    199         if self.use_defaults:
    200             self.add_defaults()
    201 
    202         if template_exists:
    203             self.read_template()
    204 
    205         if self.prune:
    206             self.prune_file_list()
    207 
    208         self.filelist.sort()
    209         self.filelist.remove_duplicates()
    210         self.write_manifest()
    211 
    212     def add_defaults(self):
    213         """Add all the default files to self.filelist:
    214           - README or README.txt
    215           - setup.py
    216           - test/test*.py
    217           - all pure Python modules mentioned in setup script
    218           - all files pointed by package_data (build_py)
    219           - all files defined in data_files.
    220           - all files defined as scripts.
    221           - all C sources listed as part of extensions or C libraries
    222             in the setup script (doesn't catch C headers!)
    223         Warns if (README or README.txt) or setup.py are missing; everything
    224         else is optional.
    225         """
    226 
    227         standards = [('README', 'README.txt'), self.distribution.script_name]
    228         for fn in standards:
    229             if isinstance(fn, tuple):
    230                 alts = fn
    231                 got_it = 0
    232                 for fn in alts:
    233                     if os.path.exists(fn):
    234                         got_it = 1
    235                         self.filelist.append(fn)
    236                         break
    237 
    238                 if not got_it:
    239                     self.warn("standard file not found: should have one of " +
    240                               string.join(alts, ', '))
    241             else:
    242                 if os.path.exists(fn):
    243                     self.filelist.append(fn)
    244                 else:
    245                     self.warn("standard file '%s' not found" % fn)
    246 
    247         optional = ['test/test*.py', 'setup.cfg']
    248         for pattern in optional:
    249             files = filter(os.path.isfile, glob(pattern))
    250             if files:
    251                 self.filelist.extend(files)
    252 
    253         # build_py is used to get:

    254         #  - python modules

    255         #  - files defined in package_data

    256         build_py = self.get_finalized_command('build_py')
    257 
    258         # getting python files

    259         if self.distribution.has_pure_modules():
    260             self.filelist.extend(build_py.get_source_files())
    261 
    262         # getting package_data files

    263         # (computed in build_py.data_files by build_py.finalize_options)

    264         for pkg, src_dir, build_dir, filenames in build_py.data_files:
    265             for filename in filenames:
    266                 self.filelist.append(os.path.join(src_dir, filename))
    267 
    268         # getting distribution.data_files

    269         if self.distribution.has_data_files():
    270             for item in self.distribution.data_files:
    271                 if isinstance(item, str): # plain file

    272                     item = convert_path(item)
    273                     if os.path.isfile(item):
    274                         self.filelist.append(item)
    275                 else:    # a (dirname, filenames) tuple

    276                     dirname, filenames = item
    277                     for f in filenames:
    278                         f = convert_path(f)
    279                         if os.path.isfile(f):
    280                             self.filelist.append(f)
    281 
    282         if self.distribution.has_ext_modules():
    283             build_ext = self.get_finalized_command('build_ext')
    284             self.filelist.extend(build_ext.get_source_files())
    285 
    286         if self.distribution.has_c_libraries():
    287             build_clib = self.get_finalized_command('build_clib')
    288             self.filelist.extend(build_clib.get_source_files())
    289 
    290         if self.distribution.has_scripts():
    291             build_scripts = self.get_finalized_command('build_scripts')
    292             self.filelist.extend(build_scripts.get_source_files())
    293 
    294     def read_template(self):
    295         """Read and parse manifest template file named by self.template.
    296 
    297         (usually "MANIFEST.in") The parsing and processing is done by
    298         'self.filelist', which updates itself accordingly.
    299         """
    300         log.info("reading manifest template '%s'", self.template)
    301         template = TextFile(self.template,
    302                             strip_comments=1,
    303                             skip_blanks=1,
    304                             join_lines=1,
    305                             lstrip_ws=1,
    306                             rstrip_ws=1,
    307                             collapse_join=1)
    308 
    309         try:
    310             while 1:
    311                 line = template.readline()
    312                 if line is None:            # end of file

    313                     break
    314 
    315                 try:
    316                     self.filelist.process_template_line(line)
    317                 except DistutilsTemplateError, msg:
    318                     self.warn("%s, line %d: %s" % (template.filename,
    319                                                    template.current_line,
    320                                                    msg))
    321         finally:
    322             template.close()
    323 
    324     def prune_file_list(self):
    325         """Prune off branches that might slip into the file list as created
    326         by 'read_template()', but really don't belong there:
    327           * the build tree (typically "build")
    328           * the release tree itself (only an issue if we ran "sdist"
    329             previously with --keep-temp, or it aborted)
    330           * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
    331         """
    332         build = self.get_finalized_command('build')
    333         base_dir = self.distribution.get_fullname()
    334 
    335         self.filelist.exclude_pattern(None, prefix=build.build_base)
    336         self.filelist.exclude_pattern(None, prefix=base_dir)
    337 
    338         # pruning out vcs directories

    339         # both separators are used under win32

    340         if sys.platform == 'win32':
    341             seps = r'/|\\'
    342         else:
    343             seps = '/'
    344 
    345         vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
    346                     '_darcs']
    347         vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
    348         self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
    349 
    350     def write_manifest(self):
    351         """Write the file list in 'self.filelist' (presumably as filled in
    352         by 'add_defaults()' and 'read_template()') to the manifest file
    353         named by 'self.manifest'.
    354         """
    355         if os.path.isfile(self.manifest):
    356             fp = open(self.manifest)
    357             try:
    358                 first_line = fp.readline()
    359             finally:
    360                 fp.close()
    361 
    362             if first_line != '# file GENERATED by distutils, do NOT edit\n':

    363                 log.info("not writing to manually maintained "
    364                          "manifest file '%s'" % self.manifest)
    365                 return
    366 
    367         content = self.filelist.files[:]
    368         content.insert(0, '# file GENERATED by distutils, do NOT edit')
    369         self.execute(file_util.write_file, (self.manifest, content),
    370                      "writing manifest file '%s'" % self.manifest)
    371 
    372     def read_manifest(self):
    373         """Read the manifest file (named by 'self.manifest') and use it to
    374         fill in 'self.filelist', the list of files to include in the source
    375         distribution.
    376         """
    377         log.info("reading manifest file '%s'", self.manifest)
    378         manifest = open(self.manifest)
    379         while 1:
    380             line = manifest.readline()
    381             if line == '':              # end of file

    382                 break
    383             if line[-1] == '\n':
    384                 line = line[0:-1]
    385             self.filelist.append(line)
    386         manifest.close()
    387 
    388     def make_release_tree(self, base_dir, files):
    389         """Create the directory tree that will become the source
    390         distribution archive.  All directories implied by the filenames in
    391         'files' are created under 'base_dir', and then we hard link or copy
    392         (if hard linking is unavailable) those files into place.
    393         Essentially, this duplicates the developer's source tree, but in a
    394         directory named after the distribution, containing only the files
    395         to be distributed.
    396         """
    397         # Create all the directories under 'base_dir' necessary to

    398         # put 'files' there; the 'mkpath()' is just so we don't die

    399         # if the manifest happens to be empty.

    400         self.mkpath(base_dir)
    401         dir_util.create_tree(base_dir, files, dry_run=self.dry_run)
    402 
    403         # And walk over the list of files, either making a hard link (if

    404         # os.link exists) to each one that doesn't already exist in its

    405         # corresponding location under 'base_dir', or copying each file

    406         # that's out-of-date in 'base_dir'.  (Usually, all files will be

    407         # out-of-date, because by default we blow away 'base_dir' when

    408         # we're done making the distribution archives.)

    409 
    410         if hasattr(os, 'link'):        # can make hard links on this system

    411             link = 'hard'
    412             msg = "making hard links in %s..." % base_dir
    413         else:                           # nope, have to copy

    414             link = None
    415             msg = "copying files to %s..." % base_dir
    416 
    417         if not files:
    418             log.warn("no files to distribute -- empty manifest?")
    419         else:
    420             log.info(msg)
    421         for file in files:
    422             if not os.path.isfile(file):
    423                 log.warn("'%s' not a regular file -- skipping" % file)
    424             else:
    425                 dest = os.path.join(base_dir, file)
    426                 self.copy_file(file, dest, link=link)
    427 
    428         self.distribution.metadata.write_pkg_info(base_dir)
    429 
    430     def make_distribution(self):
    431         """Create the source distribution(s).  First, we create the release
    432         tree with 'make_release_tree()'; then, we create all required
    433         archive files (according to 'self.formats') from the release tree.
    434         Finally, we clean up by blowing away the release tree (unless
    435         'self.keep_temp' is true).  The list of archive files created is
    436         stored so it can be retrieved later by 'get_archive_files()'.
    437         """
    438         # Don't warn about missing meta-data here -- should be (and is!)

    439         # done elsewhere.

    440         base_dir = self.distribution.get_fullname()
    441         base_name = os.path.join(self.dist_dir, base_dir)
    442 
    443         self.make_release_tree(base_dir, self.filelist.files)
    444         archive_files = []              # remember names of files we create

    445         # tar archive must be created last to avoid overwrite and remove

    446         if 'tar' in self.formats:
    447             self.formats.append(self.formats.pop(self.formats.index('tar')))
    448 
    449         for fmt in self.formats:
    450             file = self.make_archive(base_name, fmt, base_dir=base_dir,
    451                                      owner=self.owner, group=self.group)
    452             archive_files.append(file)
    453             self.distribution.dist_files.append(('sdist', '', file))
    454 
    455         self.archive_files = archive_files
    456 
    457         if not self.keep_temp:
    458             dir_util.remove_tree(base_dir, dry_run=self.dry_run)
    459 
    460     def get_archive_files(self):
    461         """Return the list of archive files created when the command
    462         was run, or None if the command hasn't run yet.
    463         """
    464         return self.archive_files
    465