sdist.py revision fa9ff76aae32ecaae6b5409a410c10ff12116732
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-temp', '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    boolean_options = ['use-defaults', 'prune',
71                       'manifest-only', 'force-manifest',
72                       'keep-temp']
73
74    help_options = [
75        ('help-formats', None,
76         "list available distribution formats", show_formats),
77        ]
78
79    negative_opt = {'no-defaults': 'use-defaults',
80                    'no-prune': 'prune' }
81
82    default_format = { 'posix': 'gztar',
83                       'nt': 'zip' }
84
85    def initialize_options (self):
86        # 'template' and 'manifest' are, respectively, the names of
87        # the manifest template and manifest file.
88        self.template = None
89        self.manifest = None
90
91        # 'use_defaults': if true, we will include the default file set
92        # in the manifest
93        self.use_defaults = 1
94        self.prune = 1
95
96        self.manifest_only = 0
97        self.force_manifest = 0
98
99        self.formats = None
100        self.keep_temp = 0
101        self.dist_dir = None
102
103        self.archive_files = None
104
105
106    def finalize_options (self):
107        if self.manifest is None:
108            self.manifest = "MANIFEST"
109        if self.template is None:
110            self.template = "MANIFEST.in"
111
112        self.ensure_string_list('formats')
113        if self.formats is None:
114            try:
115                self.formats = [self.default_format[os.name]]
116            except KeyError:
117                raise DistutilsPlatformError, \
118                      "don't know how to create source distributions " + \
119                      "on platform %s" % os.name
120
121        bad_format = archive_util.check_archive_formats(self.formats)
122        if bad_format:
123            raise DistutilsOptionError, \
124                  "unknown archive format '%s'" % bad_format
125
126        if self.dist_dir is None:
127            self.dist_dir = "dist"
128
129
130    def run (self):
131
132        # 'filelist' contains the list of files that will make up the
133        # manifest
134        self.filelist = FileList()
135
136        # Ensure that all required meta-data is given; warn if not (but
137        # don't die, it's not *that* serious!)
138        self.check_metadata()
139
140        # Do whatever it takes to get the list of files to process
141        # (process the manifest template, read an existing manifest,
142        # whatever).  File list is accumulated in 'self.filelist'.
143        self.get_file_list()
144
145        # If user just wanted us to regenerate the manifest, stop now.
146        if self.manifest_only:
147            return
148
149        # Otherwise, go ahead and create the source distribution tarball,
150        # or zipfile, or whatever.
151        self.make_distribution()
152
153
154    def check_metadata (self):
155        """Ensure that all required elements of meta-data (name, version,
156        URL, (author and author_email) or (maintainer and
157        maintainer_email)) are supplied by the Distribution object; warn if
158        any are missing.
159        """
160        metadata = self.distribution.metadata
161
162        missing = []
163        for attr in ('name', 'version', 'url'):
164            if not (hasattr(metadata, attr) and getattr(metadata, attr)):
165                missing.append(attr)
166
167        if missing:
168            self.warn("missing required meta-data: " +
169                      string.join(missing, ", "))
170
171        if metadata.author:
172            if not metadata.author_email:
173                self.warn("missing meta-data: if 'author' supplied, " +
174                          "'author_email' must be supplied too")
175        elif metadata.maintainer:
176            if not metadata.maintainer_email:
177                self.warn("missing meta-data: if 'maintainer' supplied, " +
178                          "'maintainer_email' must be supplied too")
179        else:
180            self.warn("missing meta-data: either (author and author_email) " +
181                      "or (maintainer and maintainer_email) " +
182                      "must be supplied")
183
184    # check_metadata ()
185
186
187    def get_file_list (self):
188        """Figure out the list of files to include in the source
189        distribution, and put it in 'self.filelist'.  This might involve
190        reading the manifest template (and writing the manifest), or just
191        reading the manifest, or just using the default file set -- it all
192        depends on the user's options and the state of the filesystem.
193        """
194
195        # If we have a manifest template, see if it's newer than the
196        # manifest; if so, we'll regenerate the manifest.
197        template_exists = os.path.isfile(self.template)
198        if template_exists:
199            template_newer = dep_util.newer(self.template, self.manifest)
200
201        # The contents of the manifest file almost certainly depend on the
202        # setup script as well as the manifest template -- so if the setup
203        # script is newer than the manifest, we'll regenerate the manifest
204        # from the template.  (Well, not quite: if we already have a
205        # manifest, but there's no template -- which will happen if the
206        # developer elects to generate a manifest some other way -- then we
207        # can't regenerate the manifest, so we don't.)
208        self.debug_print("checking if %s newer than %s" %
209                         (self.distribution.script_name, self.manifest))
210        setup_newer = dep_util.newer(self.distribution.script_name,
211                                     self.manifest)
212
213        # cases:
214        #   1) no manifest, template exists: generate manifest
215        #      (covered by 2a: no manifest == template newer)
216        #   2) manifest & template exist:
217        #      2a) template or setup script newer than manifest:
218        #          regenerate manifest
219        #      2b) manifest newer than both:
220        #          do nothing (unless --force or --manifest-only)
221        #   3) manifest exists, no template:
222        #      do nothing (unless --force or --manifest-only)
223        #   4) no manifest, no template: generate w/ warning ("defaults only")
224
225        manifest_outofdate = (template_exists and
226                              (template_newer or setup_newer))
227        force_regen = self.force_manifest or self.manifest_only
228        manifest_exists = os.path.isfile(self.manifest)
229        neither_exists = (not template_exists and not manifest_exists)
230
231        # Regenerate the manifest if necessary (or if explicitly told to)
232        if manifest_outofdate or neither_exists or force_regen:
233            if not template_exists:
234                self.warn(("manifest template '%s' does not exist " +
235                           "(using default file list)") %
236                          self.template)
237
238            self.filelist.findall()
239
240            # Add default file set to 'files'
241            if self.use_defaults:
242                self.add_defaults()
243
244            # Read manifest template if it exists
245            if template_exists:
246                self.read_template()
247
248            # Prune away any directories that don't belong in the source
249            # distribution
250            if self.prune:
251                self.prune_file_list()
252
253            # File list now complete -- sort it so that higher-level files
254            # come first
255            self.filelist.sort()
256
257            # Remove duplicates from the file list
258            self.filelist.remove_duplicates()
259
260            # And write complete file list (including default file set) to
261            # the manifest.
262            self.write_manifest()
263
264        # Don't regenerate the manifest, just read it in.
265        else:
266            self.read_manifest()
267
268    # get_file_list ()
269
270
271    def add_defaults (self):
272        """Add all the default files to self.filelist:
273          - README or README.txt
274          - setup.py
275          - test/test*.py
276          - all pure Python modules mentioned in setup script
277          - all C sources listed as part of extensions or C libraries
278            in the setup script (doesn't catch C headers!)
279        Warns if (README or README.txt) or setup.py are missing; everything
280        else is optional.
281        """
282
283        standards = [('README', 'README.txt'), self.distribution.script_name]
284        for fn in standards:
285            if type(fn) is TupleType:
286                alts = fn
287                got_it = 0
288                for fn in alts:
289                    if os.path.exists(fn):
290                        got_it = 1
291                        self.filelist.append(fn)
292                        break
293
294                if not got_it:
295                    self.warn("standard file not found: should have one of " +
296                              string.join(alts, ', '))
297            else:
298                if os.path.exists(fn):
299                    self.filelist.append(fn)
300                else:
301                    self.warn("standard file '%s' not found" % fn)
302
303        optional = ['test/test*.py', 'setup.cfg']
304        for pattern in optional:
305            files = filter(os.path.isfile, glob(pattern))
306            if files:
307                self.filelist.extend(files)
308
309        if self.distribution.has_pure_modules():
310            build_py = self.get_finalized_command('build_py')
311            self.filelist.extend(build_py.get_source_files())
312
313        if self.distribution.has_ext_modules():
314            build_ext = self.get_finalized_command('build_ext')
315            self.filelist.extend(build_ext.get_source_files())
316
317        if self.distribution.has_c_libraries():
318            build_clib = self.get_finalized_command('build_clib')
319            self.filelist.extend(build_clib.get_source_files())
320
321    # add_defaults ()
322
323
324    def read_template (self):
325
326        """Read and parse the manifest template file named by
327        'self.template' (usually "MANIFEST.in").  The parsing and
328        processing is done by 'self.filelist', which updates itself
329        accordingly.
330        """
331        self.announce("reading manifest template '%s'" % self.template)
332        template = TextFile(self.template,
333                            strip_comments=1,
334                            skip_blanks=1,
335                            join_lines=1,
336                            lstrip_ws=1,
337                            rstrip_ws=1,
338                            collapse_join=1)
339
340        while 1:
341            line = template.readline()
342            if line is None:            # end of file
343                break
344
345            try:
346                self.filelist.process_template_line(line)
347            except DistutilsTemplateError, msg:
348                self.warn("%s, line %d: %s" % (template.filename,
349                                               template.current_line,
350                                               msg))
351
352    # read_template ()
353
354
355    def prune_file_list (self):
356        """Prune off branches that might slip into the file list as created
357        by 'read_template()', but really don't belong there:
358          * the build tree (typically "build")
359          * the release tree itself (only an issue if we ran "sdist"
360            previously with --keep-temp, or it aborted)
361          * any RCS or CVS directories
362        """
363        build = self.get_finalized_command('build')
364        base_dir = self.distribution.get_fullname()
365
366        self.filelist.exclude_pattern(None, prefix=build.build_base)
367        self.filelist.exclude_pattern(None, prefix=base_dir)
368        self.filelist.exclude_pattern(r'/(RCS|CVS)/.*', is_regex=1)
369
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        self.announce("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
398    # read_manifest ()
399
400
401    def make_release_tree (self, base_dir, files):
402        """Create the directory tree that will become the source
403        distribution archive.  All directories implied by the filenames in
404        'files' are created under 'base_dir', and then we hard link or copy
405        (if hard linking is unavailable) those files into place.
406        Essentially, this duplicates the developer's source tree, but in a
407        directory named after the distribution, containing only the files
408        to be distributed.
409        """
410        # Create all the directories under 'base_dir' necessary to
411        # put 'files' there; the 'mkpath()' is just so we don't die
412        # if the manifest happens to be empty.
413        self.mkpath(base_dir)
414        dir_util.create_tree(base_dir, files,
415                             verbose=self.verbose, 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            self.warn("no files to distribute -- empty manifest?")
433        else:
434            self.announce(msg)
435        for file in files:
436            if not os.path.isfile(file):
437                self.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    # make_release_tree ()
443
444
445    def make_distribution (self):
446        """Create the source distribution(s).  First, we create the release
447        tree with 'make_release_tree()'; then, we create all required
448        archive files (according to 'self.formats') from the release tree.
449        Finally, we clean up by blowing away the release tree (unless
450        'self.keep_temp' is true).  The list of archive files created is
451        stored so it can be retrieved later by 'get_archive_files()'.
452        """
453        # Don't warn about missing meta-data here -- should be (and is!)
454        # done elsewhere.
455        base_dir = self.distribution.get_fullname()
456        base_name = os.path.join(self.dist_dir, base_dir)
457
458        self.make_release_tree(base_dir, self.filelist.files)
459        archive_files = []              # remember names of files we create
460        for fmt in self.formats:
461            file = self.make_archive(base_name, fmt, base_dir=base_dir)
462            archive_files.append(file)
463
464        self.archive_files = archive_files
465
466        if not self.keep_temp:
467            dir_util.remove_tree(base_dir, self.verbose, self.dry_run)
468
469    def get_archive_files (self):
470        """Return the list of archive files created when the command
471        was run, or None if the command hasn't run yet.
472        """
473        return self.archive_files
474
475# class sdist
476