~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/distutils/command/sdist.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""distutils.command.sdist
 
2
 
 
3
Implements the Distutils 'sdist' command (create a source distribution)."""
 
4
 
 
5
__revision__ = "$Id: sdist.py 69726 2009-02-17 23:10:18Z tarek.ziade $"
 
6
 
 
7
import os
 
8
import string
 
9
import sys
 
10
from types import *
 
11
from glob import glob
 
12
from distutils.core import Command
 
13
from distutils import dir_util, dep_util, file_util, archive_util
 
14
from distutils.text_file import TextFile
 
15
from distutils.errors import *
 
16
from distutils.filelist import FileList
 
17
from distutils import log
 
18
from distutils.util import convert_path
 
19
 
 
20
def show_formats ():
 
21
    """Print all possible values for the 'formats' option (used by
 
22
    the "--help-formats" command-line option).
 
23
    """
 
24
    from distutils.fancy_getopt import FancyGetopt
 
25
    from distutils.archive_util import ARCHIVE_FORMATS
 
26
    formats=[]
 
27
    for format in ARCHIVE_FORMATS.keys():
 
28
        formats.append(("formats=" + format, None,
 
29
                        ARCHIVE_FORMATS[format][2]))
 
30
    formats.sort()
 
31
    pretty_printer = FancyGetopt(formats)
 
32
    pretty_printer.print_help(
 
33
        "List of available source distribution formats:")
 
34
 
 
35
class 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
        # 'filelist' contains the list of files that will make up the
 
132
        # manifest
 
133
        self.filelist = FileList()
 
134
 
 
135
        # Ensure that all required meta-data is given; warn if not (but
 
136
        # don't die, it's not *that* serious!)
 
137
        self.check_metadata()
 
138
 
 
139
        # Do whatever it takes to get the list of files to process
 
140
        # (process the manifest template, read an existing manifest,
 
141
        # whatever).  File list is accumulated in 'self.filelist'.
 
142
        self.get_file_list()
 
143
 
 
144
        # If user just wanted us to regenerate the manifest, stop now.
 
145
        if self.manifest_only:
 
146
            return
 
147
 
 
148
        # Otherwise, go ahead and create the source distribution tarball,
 
149
        # or zipfile, or whatever.
 
150
        self.make_distribution()
 
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
                      ", ".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
    def get_file_list(self):
 
183
        """Figure out the list of files to include in the source
 
184
        distribution, and put it in 'self.filelist'.  This might involve
 
185
        reading the manifest template (and writing the manifest), or just
 
186
        reading the manifest, or just using the default file set -- it all
 
187
        depends on the user's options and the state of the filesystem.
 
188
        """
 
189
        # If we have a manifest template, see if it's newer than the
 
190
        # manifest; if so, we'll regenerate the manifest.
 
191
        template_exists = os.path.isfile(self.template)
 
192
        if template_exists:
 
193
            template_newer = dep_util.newer(self.template, self.manifest)
 
194
 
 
195
        # The contents of the manifest file almost certainly depend on the
 
196
        # setup script as well as the manifest template -- so if the setup
 
197
        # script is newer than the manifest, we'll regenerate the manifest
 
198
        # from the template.  (Well, not quite: if we already have a
 
199
        # manifest, but there's no template -- which will happen if the
 
200
        # developer elects to generate a manifest some other way -- then we
 
201
        # can't regenerate the manifest, so we don't.)
 
202
        self.debug_print("checking if %s newer than %s" %
 
203
                         (self.distribution.script_name, self.manifest))
 
204
        setup_newer = dep_util.newer(self.distribution.script_name,
 
205
                                     self.manifest)
 
206
 
 
207
        # cases:
 
208
        #   1) no manifest, template exists: generate manifest
 
209
        #      (covered by 2a: no manifest == template newer)
 
210
        #   2) manifest & template exist:
 
211
        #      2a) template or setup script newer than manifest:
 
212
        #          regenerate manifest
 
213
        #      2b) manifest newer than both:
 
214
        #          do nothing (unless --force or --manifest-only)
 
215
        #   3) manifest exists, no template:
 
216
        #      do nothing (unless --force or --manifest-only)
 
217
        #   4) no manifest, no template: generate w/ warning ("defaults only")
 
218
 
 
219
        manifest_outofdate = (template_exists and
 
220
                              (template_newer or setup_newer))
 
221
        force_regen = self.force_manifest or self.manifest_only
 
222
        manifest_exists = os.path.isfile(self.manifest)
 
223
        neither_exists = (not template_exists and not manifest_exists)
 
224
 
 
225
        # Regenerate the manifest if necessary (or if explicitly told to)
 
226
        if manifest_outofdate or neither_exists or force_regen:
 
227
            if not template_exists:
 
228
                self.warn("manifest template '%s' does not exist "
 
229
                          "(using default file list)"
 
230
                          % self.template)
 
231
            self.filelist.findall()
 
232
 
 
233
            if self.use_defaults:
 
234
                self.add_defaults()
 
235
            if template_exists:
 
236
                self.read_template()
 
237
            if self.prune:
 
238
                self.prune_file_list()
 
239
 
 
240
            self.filelist.sort()
 
241
            self.filelist.remove_duplicates()
 
242
            self.write_manifest()
 
243
 
 
244
        # Don't regenerate the manifest, just read it in.
 
245
        else:
 
246
            self.read_manifest()
 
247
 
 
248
 
 
249
    def add_defaults(self):
 
250
        """Add all the default files to self.filelist:
 
251
          - README or README.txt
 
252
          - setup.py
 
253
          - test/test*.py
 
254
          - all pure Python modules mentioned in setup script
 
255
          - all files pointed by package_data (build_py)
 
256
          - all files defined in data_files.
 
257
          - all files defined as scripts.
 
258
          - all C sources listed as part of extensions or C libraries
 
259
            in the setup script (doesn't catch C headers!)
 
260
        Warns if (README or README.txt) or setup.py are missing; everything
 
261
        else is optional.
 
262
        """
 
263
        standards = [('README', 'README.txt'), self.distribution.script_name]
 
264
        for fn in standards:
 
265
            if isinstance(fn, tuple):
 
266
                alts = fn
 
267
                got_it = False
 
268
                for fn in alts:
 
269
                    if os.path.exists(fn):
 
270
                        got_it = True
 
271
                        self.filelist.append(fn)
 
272
                        break
 
273
 
 
274
                if not got_it:
 
275
                    self.warn("standard file not found: should have one of " +
 
276
                              ', '.join(alts))
 
277
            else:
 
278
                if os.path.exists(fn):
 
279
                    self.filelist.append(fn)
 
280
                else:
 
281
                    self.warn("standard file '%s' not found" % fn)
 
282
 
 
283
        optional = ['test/test*.py', 'setup.cfg']
 
284
        for pattern in optional:
 
285
            files = filter(os.path.isfile, glob(pattern))
 
286
            if files:
 
287
                self.filelist.extend(files)
 
288
 
 
289
        # build_py is used to get:
 
290
        #  - python modules
 
291
        #  - files defined in package_data
 
292
        build_py = self.get_finalized_command('build_py')
 
293
 
 
294
        # getting python files
 
295
        if self.distribution.has_pure_modules():
 
296
            self.filelist.extend(build_py.get_source_files())
 
297
 
 
298
        # getting package_data files
 
299
        # (computed in build_py.data_files by build_py.finalize_options)
 
300
        for pkg, src_dir, build_dir, filenames in build_py.data_files:
 
301
            for filename in filenames:
 
302
                self.filelist.append(os.path.join(src_dir, filename))
 
303
 
 
304
        # getting distribution.data_files
 
305
        if self.distribution.has_data_files():
 
306
            for item in self.distribution.data_files:
 
307
                if isinstance(item, str): # plain file
 
308
                    item = convert_path(item)
 
309
                    if os.path.isfile(item):
 
310
                        self.filelist.append(item)
 
311
                else:    # a (dirname, filenames) tuple
 
312
                    dirname, filenames = item
 
313
                    for f in filenames:
 
314
                        f = convert_path(f)
 
315
                        if os.path.isfile(f):
 
316
                            self.filelist.append(f)
 
317
 
 
318
        if self.distribution.has_ext_modules():
 
319
            build_ext = self.get_finalized_command('build_ext')
 
320
            self.filelist.extend(build_ext.get_source_files())
 
321
 
 
322
        if self.distribution.has_c_libraries():
 
323
            build_clib = self.get_finalized_command('build_clib')
 
324
            self.filelist.extend(build_clib.get_source_files())
 
325
 
 
326
        if self.distribution.has_scripts():
 
327
            build_scripts = self.get_finalized_command('build_scripts')
 
328
            self.filelist.extend(build_scripts.get_source_files())
 
329
 
 
330
    def read_template(self):
 
331
        """Read and parse manifest template file named by self.template.
 
332
 
 
333
        (usually "MANIFEST.in") The parsing and processing is done by
 
334
        'self.filelist', which updates itself accordingly.
 
335
        """
 
336
        log.info("reading manifest template '%s'", self.template)
 
337
        template = TextFile(self.template, strip_comments=1, skip_blanks=1,
 
338
                            join_lines=1, lstrip_ws=1, rstrip_ws=1,
 
339
                            collapse_join=1)
 
340
 
 
341
        while True:
 
342
            line = template.readline()
 
343
            if line is None:            # end of file
 
344
                break
 
345
 
 
346
            try:
 
347
                self.filelist.process_template_line(line)
 
348
            except DistutilsTemplateError as msg:
 
349
                self.warn("%s, line %d: %s" % (template.filename,
 
350
                                               template.current_line,
 
351
                                               msg))
 
352
 
 
353
    def prune_file_list(self):
 
354
        """Prune off branches that might slip into the file list as created
 
355
        by 'read_template()', but really don't belong there:
 
356
          * the build tree (typically "build")
 
357
          * the release tree itself (only an issue if we ran "sdist"
 
358
            previously with --keep-temp, or it aborted)
 
359
          * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
 
360
        """
 
361
        build = self.get_finalized_command('build')
 
362
        base_dir = self.distribution.get_fullname()
 
363
 
 
364
        self.filelist.exclude_pattern(None, prefix=build.build_base)
 
365
        self.filelist.exclude_pattern(None, prefix=base_dir)
 
366
 
 
367
        if sys.platform == 'win32':
 
368
            seps = r'/|\\'
 
369
        else:
 
370
            seps = '/'
 
371
 
 
372
        vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
 
373
                    '_darcs']
 
374
        vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
 
375
        self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
 
376
 
 
377
    def write_manifest (self):
 
378
        """Write the file list in 'self.filelist' (presumably as filled in
 
379
        by 'add_defaults()' and 'read_template()') to the manifest file
 
380
        named by 'self.manifest'.
 
381
        """
 
382
        self.execute(file_util.write_file,
 
383
                     (self.manifest, self.filelist.files),
 
384
                     "writing manifest file '%s'" % self.manifest)
 
385
 
 
386
    def read_manifest(self):
 
387
        """Read the manifest file (named by 'self.manifest') and use it to
 
388
        fill in 'self.filelist', the list of files to include in the source
 
389
        distribution.
 
390
        """
 
391
        log.info("reading manifest file '%s'", self.manifest)
 
392
        manifest = open(self.manifest)
 
393
        while True:
 
394
            line = manifest.readline()
 
395
            if line == '':              # end of file
 
396
                break
 
397
            if line[-1] == '\n':
 
398
                line = line[0:-1]
 
399
            self.filelist.append(line)
 
400
        manifest.close()
 
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
    def make_distribution(self):
 
445
        """Create the source distribution(s).  First, we create the release
 
446
        tree with 'make_release_tree()'; then, we create all required
 
447
        archive files (according to 'self.formats') from the release tree.
 
448
        Finally, we clean up by blowing away the release tree (unless
 
449
        'self.keep_temp' is true).  The list of archive files created is
 
450
        stored so it can be retrieved later by 'get_archive_files()'.
 
451
        """
 
452
        # Don't warn about missing meta-data here -- should be (and is!)
 
453
        # done elsewhere.
 
454
        base_dir = self.distribution.get_fullname()
 
455
        base_name = os.path.join(self.dist_dir, base_dir)
 
456
 
 
457
        self.make_release_tree(base_dir, self.filelist.files)
 
458
        archive_files = []              # remember names of files we create
 
459
        # tar archive must be created last to avoid overwrite and remove
 
460
        if 'tar' in self.formats:
 
461
            self.formats.append(self.formats.pop(self.formats.index('tar')))
 
462
 
 
463
        for fmt in self.formats:
 
464
            file = self.make_archive(base_name, fmt, base_dir=base_dir)
 
465
            archive_files.append(file)
 
466
            self.distribution.dist_files.append(('sdist', '', file))
 
467
 
 
468
        self.archive_files = archive_files
 
469
 
 
470
        if not self.keep_temp:
 
471
            dir_util.remove_tree(base_dir, dry_run=self.dry_run)
 
472
 
 
473
    def get_archive_files(self):
 
474
        """Return the list of archive files created when the command
 
475
        was run, or None if the command hasn't run yet.
 
476
        """
 
477
        return self.archive_files