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