1
"""distutils.command.sdist
3
Implements the Distutils 'sdist' command (create a source distribution)."""
5
__revision__ = "$Id: sdist.py 69726 2009-02-17 23:10:18Z tarek.ziade $"
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
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"
131
# 'filelist' contains the list of files that will make up the
133
self.filelist = FileList()
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()
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'.
144
# If user just wanted us to regenerate the manifest, stop now.
145
if self.manifest_only:
148
# Otherwise, go ahead and create the source distribution tarball,
149
# or zipfile, or whatever.
150
self.make_distribution()
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
158
metadata = self.distribution.metadata
161
for attr in ('name', 'version', 'url'):
162
if not (hasattr(metadata, attr) and getattr(metadata, attr)):
166
self.warn("missing required meta-data: " +
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")
178
self.warn("missing meta-data: either (author and author_email) " +
179
"or (maintainer and maintainer_email) " +
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.
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)
193
template_newer = dep_util.newer(self.template, self.manifest)
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,
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")
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)
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)"
231
self.filelist.findall()
233
if self.use_defaults:
238
self.prune_file_list()
241
self.filelist.remove_duplicates()
242
self.write_manifest()
244
# Don't regenerate the manifest, just read it in.
249
def add_defaults(self):
250
"""Add all the default files to self.filelist:
251
- README or README.txt
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
263
standards = [('README', 'README.txt'), self.distribution.script_name]
265
if isinstance(fn, tuple):
269
if os.path.exists(fn):
271
self.filelist.append(fn)
275
self.warn("standard file not found: should have one of " +
278
if os.path.exists(fn):
279
self.filelist.append(fn)
281
self.warn("standard file '%s' not found" % fn)
283
optional = ['test/test*.py', 'setup.cfg']
284
for pattern in optional:
285
files = filter(os.path.isfile, glob(pattern))
287
self.filelist.extend(files)
289
# build_py is used to get:
291
# - files defined in package_data
292
build_py = self.get_finalized_command('build_py')
294
# getting python files
295
if self.distribution.has_pure_modules():
296
self.filelist.extend(build_py.get_source_files())
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))
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
315
if os.path.isfile(f):
316
self.filelist.append(f)
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())
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())
326
if self.distribution.has_scripts():
327
build_scripts = self.get_finalized_command('build_scripts')
328
self.filelist.extend(build_scripts.get_source_files())
330
def read_template(self):
331
"""Read and parse manifest template file named by self.template.
333
(usually "MANIFEST.in") The parsing and processing is done by
334
'self.filelist', which updates itself accordingly.
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,
342
line = template.readline()
343
if line is None: # end of file
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,
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
361
build = self.get_finalized_command('build')
362
base_dir = self.distribution.get_fullname()
364
self.filelist.exclude_pattern(None, prefix=build.build_base)
365
self.filelist.exclude_pattern(None, prefix=base_dir)
367
if sys.platform == 'win32':
372
vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
374
vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
375
self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
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'.
382
self.execute(file_util.write_file,
383
(self.manifest, self.filelist.files),
384
"writing manifest file '%s'" % self.manifest)
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
391
log.info("reading manifest file '%s'", self.manifest)
392
manifest = open(self.manifest)
394
line = manifest.readline()
395
if line == '': # end of file
399
self.filelist.append(line)
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
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)
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.)
424
if hasattr(os, 'link'): # can make hard links on this system
426
msg = "making hard links in %s..." % base_dir
427
else: # nope, have to copy
429
msg = "copying files to %s..." % base_dir
432
log.warn("no files to distribute -- empty manifest?")
436
if not os.path.isfile(file):
437
log.warn("'%s' not a regular file -- skipping" % file)
439
dest = os.path.join(base_dir, file)
440
self.copy_file(file, dest, link=link)
442
self.distribution.metadata.write_pkg_info(base_dir)
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()'.
452
# Don't warn about missing meta-data here -- should be (and is!)
454
base_dir = self.distribution.get_fullname()
455
base_name = os.path.join(self.dist_dir, base_dir)
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')))
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))
468
self.archive_files = archive_files
470
if not self.keep_temp:
471
dir_util.remove_tree(base_dir, dry_run=self.dry_run)
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.
477
return self.archive_files