PageRenderTime 42ms CodeModel.GetById 18ms app.highlight 18ms RepoModel.GetById 2ms app.codeStats 0ms

/Lib/distutils/command/sdist.py

http://unladen-swallow.googlecode.com/
Python | 481 lines | 479 code | 0 blank | 2 comment | 11 complexity | 0434dbed50961be06c51a804402abc89 MD5 | raw file
  1"""distutils.command.sdist
  2
  3Implements the Distutils 'sdist' command (create a source distribution)."""
  4
  5# This module should be kept compatible with Python 2.1.
  6
  7__revision__ = "$Id: sdist.py 68968 2009-01-26 17:20:15Z tarek.ziade $"
  8
  9import os, string
 10import sys
 11from types import *
 12from glob import glob
 13from distutils.core import Command
 14from distutils import dir_util, dep_util, file_util, archive_util
 15from distutils.text_file import TextFile
 16from distutils.errors import *
 17from distutils.filelist import FileList
 18from distutils import log
 19
 20
 21def show_formats ():
 22    """Print all possible values for the 'formats' option (used by
 23    the "--help-formats" command-line option).
 24    """
 25    from distutils.fancy_getopt import FancyGetopt
 26    from distutils.archive_util import ARCHIVE_FORMATS
 27    formats=[]
 28    for format in ARCHIVE_FORMATS.keys():
 29        formats.append(("formats=" + format, None,
 30                        ARCHIVE_FORMATS[format][2]))
 31    formats.sort()
 32    pretty_printer = FancyGetopt(formats)
 33    pretty_printer.print_help(
 34        "List of available source distribution formats:")
 35
 36class sdist (Command):
 37
 38    description = "create a source distribution (tarball, zip file, etc.)"
 39
 40    user_options = [
 41        ('template=', 't',
 42         "name of manifest template file [default: MANIFEST.in]"),
 43        ('manifest=', 'm',
 44         "name of manifest file [default: MANIFEST]"),
 45        ('use-defaults', None,
 46         "include the default file set in the manifest "
 47         "[default; disable with --no-defaults]"),
 48        ('no-defaults', None,
 49         "don't include the default file set"),
 50        ('prune', None,
 51         "specifically exclude files/directories that should not be "
 52         "distributed (build tree, RCS/CVS dirs, etc.) "
 53         "[default; disable with --no-prune]"),
 54        ('no-prune', None,
 55         "don't automatically exclude anything"),
 56        ('manifest-only', 'o',
 57         "just regenerate the manifest and then stop "
 58         "(implies --force-manifest)"),
 59        ('force-manifest', 'f',
 60         "forcibly regenerate the manifest and carry on as usual"),
 61        ('formats=', None,
 62         "formats for source distribution (comma-separated list)"),
 63        ('keep-temp', 'k',
 64         "keep the distribution tree around after creating " +
 65         "archive file(s)"),
 66        ('dist-dir=', 'd',
 67         "directory to put the source distribution archive(s) in "
 68         "[default: dist]"),
 69        ]
 70
 71    boolean_options = ['use-defaults', 'prune',
 72                       'manifest-only', 'force-manifest',
 73                       'keep-temp']
 74
 75    help_options = [
 76        ('help-formats', None,
 77         "list available distribution formats", show_formats),
 78        ]
 79
 80    negative_opt = {'no-defaults': 'use-defaults',
 81                    'no-prune': 'prune' }
 82
 83    default_format = { 'posix': 'gztar',
 84                       'nt': 'zip' }
 85
 86    def initialize_options (self):
 87        # 'template' and 'manifest' are, respectively, the names of
 88        # the manifest template and manifest file.
 89        self.template = None
 90        self.manifest = None
 91
 92        # 'use_defaults': if true, we will include the default file set
 93        # in the manifest
 94        self.use_defaults = 1
 95        self.prune = 1
 96
 97        self.manifest_only = 0
 98        self.force_manifest = 0
 99
100        self.formats = None
101        self.keep_temp = 0
102        self.dist_dir = None
103
104        self.archive_files = None
105
106
107    def finalize_options (self):
108        if self.manifest is None:
109            self.manifest = "MANIFEST"
110        if self.template is None:
111            self.template = "MANIFEST.in"
112
113        self.ensure_string_list('formats')
114        if self.formats is None:
115            try:
116                self.formats = [self.default_format[os.name]]
117            except KeyError:
118                raise DistutilsPlatformError, \
119                      "don't know how to create source distributions " + \
120                      "on platform %s" % os.name
121
122        bad_format = archive_util.check_archive_formats(self.formats)
123        if bad_format:
124            raise DistutilsOptionError, \
125                  "unknown archive format '%s'" % bad_format
126
127        if self.dist_dir is None:
128            self.dist_dir = "dist"
129
130
131    def run (self):
132
133        # 'filelist' contains the list of files that will make up the
134        # manifest
135        self.filelist = FileList()
136
137        # Ensure that all required meta-data is given; warn if not (but
138        # don't die, it's not *that* serious!)
139        self.check_metadata()
140
141        # Do whatever it takes to get the list of files to process
142        # (process the manifest template, read an existing manifest,
143        # whatever).  File list is accumulated in 'self.filelist'.
144        self.get_file_list()
145
146        # If user just wanted us to regenerate the manifest, stop now.
147        if self.manifest_only:
148            return
149
150        # Otherwise, go ahead and create the source distribution tarball,
151        # or zipfile, or whatever.
152        self.make_distribution()
153
154
155    def check_metadata (self):
156        """Ensure that all required elements of meta-data (name, version,
157        URL, (author and author_email) or (maintainer and
158        maintainer_email)) are supplied by the Distribution object; warn if
159        any are missing.
160        """
161        metadata = self.distribution.metadata
162
163        missing = []
164        for attr in ('name', 'version', 'url'):
165            if not (hasattr(metadata, attr) and getattr(metadata, attr)):
166                missing.append(attr)
167
168        if missing:
169            self.warn("missing required meta-data: " +
170                      string.join(missing, ", "))
171
172        if metadata.author:
173            if not metadata.author_email:
174                self.warn("missing meta-data: if 'author' supplied, " +
175                          "'author_email' must be supplied too")
176        elif metadata.maintainer:
177            if not metadata.maintainer_email:
178                self.warn("missing meta-data: if 'maintainer' supplied, " +
179                          "'maintainer_email' must be supplied too")
180        else:
181            self.warn("missing meta-data: either (author and author_email) " +
182                      "or (maintainer and maintainer_email) " +
183                      "must be supplied")
184
185    # check_metadata ()
186
187
188    def get_file_list (self):
189        """Figure out the list of files to include in the source
190        distribution, and put it in 'self.filelist'.  This might involve
191        reading the manifest template (and writing the manifest), or just
192        reading the manifest, or just using the default file set -- it all
193        depends on the user's options and the state of the filesystem.
194        """
195
196        # If we have a manifest template, see if it's newer than the
197        # manifest; if so, we'll regenerate the manifest.
198        template_exists = os.path.isfile(self.template)
199        if template_exists:
200            template_newer = dep_util.newer(self.template, self.manifest)
201
202        # The contents of the manifest file almost certainly depend on the
203        # setup script as well as the manifest template -- so if the setup
204        # script is newer than the manifest, we'll regenerate the manifest
205        # from the template.  (Well, not quite: if we already have a
206        # manifest, but there's no template -- which will happen if the
207        # developer elects to generate a manifest some other way -- then we
208        # can't regenerate the manifest, so we don't.)
209        self.debug_print("checking if %s newer than %s" %
210                         (self.distribution.script_name, self.manifest))
211        setup_newer = dep_util.newer(self.distribution.script_name,
212                                     self.manifest)
213
214        # cases:
215        #   1) no manifest, template exists: generate manifest
216        #      (covered by 2a: no manifest == template newer)
217        #   2) manifest & template exist:
218        #      2a) template or setup script newer than manifest:
219        #          regenerate manifest
220        #      2b) manifest newer than both:
221        #          do nothing (unless --force or --manifest-only)
222        #   3) manifest exists, no template:
223        #      do nothing (unless --force or --manifest-only)
224        #   4) no manifest, no template: generate w/ warning ("defaults only")
225
226        manifest_outofdate = (template_exists and
227                              (template_newer or setup_newer))
228        force_regen = self.force_manifest or self.manifest_only
229        manifest_exists = os.path.isfile(self.manifest)
230        neither_exists = (not template_exists and not manifest_exists)
231
232        # Regenerate the manifest if necessary (or if explicitly told to)
233        if manifest_outofdate or neither_exists or force_regen:
234            if not template_exists:
235                self.warn(("manifest template '%s' does not exist " +
236                           "(using default file list)") %
237                          self.template)
238            self.filelist.findall()
239
240            if self.use_defaults:
241                self.add_defaults()
242            if template_exists:
243                self.read_template()
244            if self.prune:
245                self.prune_file_list()
246
247            self.filelist.sort()
248            self.filelist.remove_duplicates()
249            self.write_manifest()
250
251        # Don't regenerate the manifest, just read it in.
252        else:
253            self.read_manifest()
254
255    # get_file_list ()
256
257
258    def add_defaults (self):
259        """Add all the default files to self.filelist:
260          - README or README.txt
261          - setup.py
262          - test/test*.py
263          - all pure Python modules mentioned in setup script
264          - all C sources listed as part of extensions or C libraries
265            in the setup script (doesn't catch C headers!)
266        Warns if (README or README.txt) or setup.py are missing; everything
267        else is optional.
268        """
269
270        standards = [('README', 'README.txt'), self.distribution.script_name]
271        for fn in standards:
272            if type(fn) is TupleType:
273                alts = fn
274                got_it = 0
275                for fn in alts:
276                    if os.path.exists(fn):
277                        got_it = 1
278                        self.filelist.append(fn)
279                        break
280
281                if not got_it:
282                    self.warn("standard file not found: should have one of " +
283                              string.join(alts, ', '))
284            else:
285                if os.path.exists(fn):
286                    self.filelist.append(fn)
287                else:
288                    self.warn("standard file '%s' not found" % fn)
289
290        optional = ['test/test*.py', 'setup.cfg']
291        for pattern in optional:
292            files = filter(os.path.isfile, glob(pattern))
293            if files:
294                self.filelist.extend(files)
295
296        if self.distribution.has_pure_modules():
297            build_py = self.get_finalized_command('build_py')
298            self.filelist.extend(build_py.get_source_files())
299
300        if self.distribution.has_ext_modules():
301            build_ext = self.get_finalized_command('build_ext')
302            self.filelist.extend(build_ext.get_source_files())
303
304        if self.distribution.has_c_libraries():
305            build_clib = self.get_finalized_command('build_clib')
306            self.filelist.extend(build_clib.get_source_files())
307
308        if self.distribution.has_scripts():
309            build_scripts = self.get_finalized_command('build_scripts')
310            self.filelist.extend(build_scripts.get_source_files())
311
312    # add_defaults ()
313
314
315    def read_template (self):
316        """Read and parse manifest template file named by self.template.
317
318        (usually "MANIFEST.in") The parsing and processing is done by
319        'self.filelist', which updates itself accordingly.
320        """
321        log.info("reading manifest template '%s'", self.template)
322        template = TextFile(self.template,
323                            strip_comments=1,
324                            skip_blanks=1,
325                            join_lines=1,
326                            lstrip_ws=1,
327                            rstrip_ws=1,
328                            collapse_join=1)
329
330        while 1:
331            line = template.readline()
332            if line is None:            # end of file
333                break
334
335            try:
336                self.filelist.process_template_line(line)
337            except DistutilsTemplateError, msg:
338                self.warn("%s, line %d: %s" % (template.filename,
339                                               template.current_line,
340                                               msg))
341
342    # read_template ()
343
344
345    def prune_file_list (self):
346        """Prune off branches that might slip into the file list as created
347        by 'read_template()', but really don't belong there:
348          * the build tree (typically "build")
349          * the release tree itself (only an issue if we ran "sdist"
350            previously with --keep-temp, or it aborted)
351          * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
352        """
353        build = self.get_finalized_command('build')
354        base_dir = self.distribution.get_fullname()
355
356        self.filelist.exclude_pattern(None, prefix=build.build_base)
357        self.filelist.exclude_pattern(None, prefix=base_dir)
358
359        # pruning out vcs directories
360        # both separators are used under win32
361        if sys.platform == 'win32':
362            seps = r'/|\\'
363        else:
364            seps = '/'
365
366        vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
367                    '_darcs']
368        vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
369        self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
370
371    def write_manifest (self):
372        """Write the file list in 'self.filelist' (presumably as filled in
373        by 'add_defaults()' and 'read_template()') to the manifest file
374        named by 'self.manifest'.
375        """
376        self.execute(file_util.write_file,
377                     (self.manifest, self.filelist.files),
378                     "writing manifest file '%s'" % self.manifest)
379
380    # write_manifest ()
381
382
383    def read_manifest (self):
384        """Read the manifest file (named by 'self.manifest') and use it to
385        fill in 'self.filelist', the list of files to include in the source
386        distribution.
387        """
388        log.info("reading manifest file '%s'", self.manifest)
389        manifest = open(self.manifest)
390        while 1:
391            line = manifest.readline()
392            if line == '':              # end of file
393                break
394            if line[-1] == '\n':
395                line = line[0:-1]
396            self.filelist.append(line)
397        manifest.close()
398
399    # read_manifest ()
400
401
402    def make_release_tree (self, base_dir, files):
403        """Create the directory tree that will become the source
404        distribution archive.  All directories implied by the filenames in
405        'files' are created under 'base_dir', and then we hard link or copy
406        (if hard linking is unavailable) those files into place.
407        Essentially, this duplicates the developer's source tree, but in a
408        directory named after the distribution, containing only the files
409        to be distributed.
410        """
411        # Create all the directories under 'base_dir' necessary to
412        # put 'files' there; the 'mkpath()' is just so we don't die
413        # if the manifest happens to be empty.
414        self.mkpath(base_dir)
415        dir_util.create_tree(base_dir, files, dry_run=self.dry_run)
416
417        # And walk over the list of files, either making a hard link (if
418        # os.link exists) to each one that doesn't already exist in its
419        # corresponding location under 'base_dir', or copying each file
420        # that's out-of-date in 'base_dir'.  (Usually, all files will be
421        # out-of-date, because by default we blow away 'base_dir' when
422        # we're done making the distribution archives.)
423
424        if hasattr(os, 'link'):        # can make hard links on this system
425            link = 'hard'
426            msg = "making hard links in %s..." % base_dir
427        else:                           # nope, have to copy
428            link = None
429            msg = "copying files to %s..." % base_dir
430
431        if not files:
432            log.warn("no files to distribute -- empty manifest?")
433        else:
434            log.info(msg)
435        for file in files:
436            if not os.path.isfile(file):
437                log.warn("'%s' not a regular file -- skipping" % file)
438            else:
439                dest = os.path.join(base_dir, file)
440                self.copy_file(file, dest, link=link)
441
442        self.distribution.metadata.write_pkg_info(base_dir)
443
444    # make_release_tree ()
445
446    def make_distribution (self):
447        """Create the source distribution(s).  First, we create the release
448        tree with 'make_release_tree()'; then, we create all required
449        archive files (according to 'self.formats') from the release tree.
450        Finally, we clean up by blowing away the release tree (unless
451        'self.keep_temp' is true).  The list of archive files created is
452        stored so it can be retrieved later by 'get_archive_files()'.
453        """
454        # Don't warn about missing meta-data here -- should be (and is!)
455        # done elsewhere.
456        base_dir = self.distribution.get_fullname()
457        base_name = os.path.join(self.dist_dir, base_dir)
458
459        self.make_release_tree(base_dir, self.filelist.files)
460        archive_files = []              # remember names of files we create
461        # tar archive must be created last to avoid overwrite and remove
462        if 'tar' in self.formats:
463            self.formats.append(self.formats.pop(self.formats.index('tar')))
464
465        for fmt in self.formats:
466            file = self.make_archive(base_name, fmt, base_dir=base_dir)
467            archive_files.append(file)
468            self.distribution.dist_files.append(('sdist', '', file))
469
470        self.archive_files = archive_files
471
472        if not self.keep_temp:
473            dir_util.remove_tree(base_dir, dry_run=self.dry_run)
474
475    def get_archive_files (self):
476        """Return the list of archive files created when the command
477        was run, or None if the command hasn't run yet.
478        """
479        return self.archive_files
480
481# class sdist