1
"""distutils.command.sdist
3
Implements the Distutils 'sdist' command (create a source distribution)."""
5
# This module should be kept compatible with Python 2.1.
7
__revision__ = "$Id: sdist.py 61263 2008-03-06 06:47:18Z georg.brandl $"
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
21
"""Print all possible values for the 'formats' option (used by
22
the "--help-formats" command-line option).
24
from distutils.fancy_getopt import FancyGetopt
25
from distutils.archive_util import ARCHIVE_FORMATS
27
for format in ARCHIVE_FORMATS.keys():
28
formats.append(("formats=" + format, None,
29
ARCHIVE_FORMATS[format][2]))
31
pretty_printer = FancyGetopt(formats)
32
pretty_printer.print_help(
33
"List of available source distribution formats:")
35
class sdist (Command):
37
description = "create a source distribution (tarball, zip file, etc.)"
41
"name of manifest template file [default: MANIFEST.in]"),
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]"),
48
"don't include the default file set"),
50
"specifically exclude files/directories that should not be "
51
"distributed (build tree, RCS/CVS dirs, etc.) "
52
"[default; disable with --no-prune]"),
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"),
61
"formats for source distribution (comma-separated list)"),
63
"keep the distribution tree around after creating " +
66
"directory to put the source distribution archive(s) in "
70
boolean_options = ['use-defaults', 'prune',
71
'manifest-only', 'force-manifest',
75
('help-formats', None,
76
"list available distribution formats", show_formats),
79
negative_opt = {'no-defaults': 'use-defaults',
82
default_format = { 'posix': 'gztar',
85
def initialize_options (self):
86
# 'template' and 'manifest' are, respectively, the names of
87
# the manifest template and manifest file.
91
# 'use_defaults': if true, we will include the default file set
96
self.manifest_only = 0
97
self.force_manifest = 0
103
self.archive_files = None
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"
112
self.ensure_string_list('formats')
113
if self.formats is None:
115
self.formats = [self.default_format[os.name]]
117
raise DistutilsPlatformError, \
118
"don't know how to create source distributions " + \
119
"on platform %s" % os.name
121
bad_format = archive_util.check_archive_formats(self.formats)
123
raise DistutilsOptionError, \
124
"unknown archive format '%s'" % bad_format
126
if self.dist_dir is None:
127
self.dist_dir = "dist"
132
# 'filelist' contains the list of files that will make up the
134
self.filelist = FileList()
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()
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'.
145
# If user just wanted us to regenerate the manifest, stop now.
146
if self.manifest_only:
149
# Otherwise, go ahead and create the source distribution tarball,
150
# or zipfile, or whatever.
151
self.make_distribution()
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
160
metadata = self.distribution.metadata
163
for attr in ('name', 'version', 'url'):
164
if not (hasattr(metadata, attr) and getattr(metadata, attr)):
168
self.warn("missing required meta-data: " +
169
string.join(missing, ", "))
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")
180
self.warn("missing meta-data: either (author and author_email) " +
181
"or (maintainer and maintainer_email) " +
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.
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)
199
template_newer = dep_util.newer(self.template, self.manifest)
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,
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")
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)
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)") %
237
self.filelist.findall()
239
if self.use_defaults:
244
self.prune_file_list()
247
self.filelist.remove_duplicates()
248
self.write_manifest()
250
# Don't regenerate the manifest, just read it in.
257
def add_defaults (self):
258
"""Add all the default files to self.filelist:
259
- README or README.txt
262
- all pure Python modules mentioned in setup script
263
- all C sources listed as part of extensions or C libraries
264
in the setup script (doesn't catch C headers!)
265
Warns if (README or README.txt) or setup.py are missing; everything
269
standards = [('README', 'README.txt'), self.distribution.script_name]
271
if type(fn) is TupleType:
275
if os.path.exists(fn):
277
self.filelist.append(fn)
281
self.warn("standard file not found: should have one of " +
282
string.join(alts, ', '))
284
if os.path.exists(fn):
285
self.filelist.append(fn)
287
self.warn("standard file '%s' not found" % fn)
289
optional = ['test/test*.py', 'setup.cfg']
290
for pattern in optional:
291
files = filter(os.path.isfile, glob(pattern))
293
self.filelist.extend(files)
295
if self.distribution.has_pure_modules():
296
build_py = self.get_finalized_command('build_py')
297
self.filelist.extend(build_py.get_source_files())
299
if self.distribution.has_ext_modules():
300
build_ext = self.get_finalized_command('build_ext')
301
self.filelist.extend(build_ext.get_source_files())
303
if self.distribution.has_c_libraries():
304
build_clib = self.get_finalized_command('build_clib')
305
self.filelist.extend(build_clib.get_source_files())
307
if self.distribution.has_scripts():
308
build_scripts = self.get_finalized_command('build_scripts')
309
self.filelist.extend(build_scripts.get_source_files())
314
def read_template (self):
315
"""Read and parse manifest template file named by self.template.
317
(usually "MANIFEST.in") The parsing and processing is done by
318
'self.filelist', which updates itself accordingly.
320
log.info("reading manifest template '%s'", self.template)
321
template = TextFile(self.template,
330
line = template.readline()
331
if line is None: # end of file
335
self.filelist.process_template_line(line)
336
except DistutilsTemplateError, msg:
337
self.warn("%s, line %d: %s" % (template.filename,
338
template.current_line,
344
def prune_file_list (self):
345
"""Prune off branches that might slip into the file list as created
346
by 'read_template()', but really don't belong there:
347
* the build tree (typically "build")
348
* the release tree itself (only an issue if we ran "sdist"
349
previously with --keep-temp, or it aborted)
350
* any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
352
build = self.get_finalized_command('build')
353
base_dir = self.distribution.get_fullname()
355
self.filelist.exclude_pattern(None, prefix=build.build_base)
356
self.filelist.exclude_pattern(None, prefix=base_dir)
357
self.filelist.exclude_pattern(r'(^|/)(RCS|CVS|\.svn|\.hg|\.git|\.bzr|_darcs)/.*', is_regex=1)
360
def write_manifest (self):
361
"""Write the file list in 'self.filelist' (presumably as filled in
362
by 'add_defaults()' and 'read_template()') to the manifest file
363
named by 'self.manifest'.
365
self.execute(file_util.write_file,
366
(self.manifest, self.filelist.files),
367
"writing manifest file '%s'" % self.manifest)
372
def read_manifest (self):
373
"""Read the manifest file (named by 'self.manifest') and use it to
374
fill in 'self.filelist', the list of files to include in the source
377
log.info("reading manifest file '%s'", self.manifest)
378
manifest = open(self.manifest)
380
line = manifest.readline()
381
if line == '': # end of file
385
self.filelist.append(line)
391
def make_release_tree (self, base_dir, files):
392
"""Create the directory tree that will become the source
393
distribution archive. All directories implied by the filenames in
394
'files' are created under 'base_dir', and then we hard link or copy
395
(if hard linking is unavailable) those files into place.
396
Essentially, this duplicates the developer's source tree, but in a
397
directory named after the distribution, containing only the files
400
# Create all the directories under 'base_dir' necessary to
401
# put 'files' there; the 'mkpath()' is just so we don't die
402
# if the manifest happens to be empty.
403
self.mkpath(base_dir)
404
dir_util.create_tree(base_dir, files, dry_run=self.dry_run)
406
# And walk over the list of files, either making a hard link (if
407
# os.link exists) to each one that doesn't already exist in its
408
# corresponding location under 'base_dir', or copying each file
409
# that's out-of-date in 'base_dir'. (Usually, all files will be
410
# out-of-date, because by default we blow away 'base_dir' when
411
# we're done making the distribution archives.)
413
if hasattr(os, 'link'): # can make hard links on this system
415
msg = "making hard links in %s..." % base_dir
416
else: # nope, have to copy
418
msg = "copying files to %s..." % base_dir
421
log.warn("no files to distribute -- empty manifest?")
425
if not os.path.isfile(file):
426
log.warn("'%s' not a regular file -- skipping" % file)
428
dest = os.path.join(base_dir, file)
429
self.copy_file(file, dest, link=link)
431
self.distribution.metadata.write_pkg_info(base_dir)
433
# make_release_tree ()
435
def make_distribution (self):
436
"""Create the source distribution(s). First, we create the release
437
tree with 'make_release_tree()'; then, we create all required
438
archive files (according to 'self.formats') from the release tree.
439
Finally, we clean up by blowing away the release tree (unless
440
'self.keep_temp' is true). The list of archive files created is
441
stored so it can be retrieved later by 'get_archive_files()'.
443
# Don't warn about missing meta-data here -- should be (and is!)
445
base_dir = self.distribution.get_fullname()
446
base_name = os.path.join(self.dist_dir, base_dir)
448
self.make_release_tree(base_dir, self.filelist.files)
449
archive_files = [] # remember names of files we create
450
for fmt in self.formats:
451
file = self.make_archive(base_name, fmt, base_dir=base_dir)
452
archive_files.append(file)
453
self.distribution.dist_files.append(('sdist', '', file))
455
self.archive_files = archive_files
457
if not self.keep_temp:
458
dir_util.remove_tree(base_dir, dry_run=self.dry_run)
460
def get_archive_files (self):
461
"""Return the list of archive files created when the command
462
was run, or None if the command hasn't run yet.
464
return self.archive_files