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