sdist.py revision 1df03405fc30604e00d506ac0f8c112517d8526e
1"""distutils.command.sdist 2 3Implements the Distutils 'sdist' command (create a source distribution).""" 4 5# This module should be kept compatible with Python 2.1. 6 7__revision__ = "$Id$" 8 9import 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 17from distutils import log 18 19 20def 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 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 self.filelist.findall() 238 239 if self.use_defaults: 240 self.add_defaults() 241 if template_exists: 242 self.read_template() 243 if self.prune: 244 self.prune_file_list() 245 246 self.filelist.sort() 247 self.filelist.remove_duplicates() 248 self.write_manifest() 249 250 # Don't regenerate the manifest, just read it in. 251 else: 252 self.read_manifest() 253 254 # get_file_list () 255 256 257 def add_defaults (self): 258 """Add all the default files to self.filelist: 259 - README or README.txt 260 - setup.py 261 - test/test*.py 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 266 else is optional. 267 """ 268 269 standards = [('README', 'README.txt'), self.distribution.script_name] 270 for fn in standards: 271 if type(fn) is TupleType: 272 alts = fn 273 got_it = 0 274 for fn in alts: 275 if os.path.exists(fn): 276 got_it = 1 277 self.filelist.append(fn) 278 break 279 280 if not got_it: 281 self.warn("standard file not found: should have one of " + 282 string.join(alts, ', ')) 283 else: 284 if os.path.exists(fn): 285 self.filelist.append(fn) 286 else: 287 self.warn("standard file '%s' not found" % fn) 288 289 optional = ['test/test*.py', 'setup.cfg'] 290 for pattern in optional: 291 files = filter(os.path.isfile, glob(pattern)) 292 if files: 293 self.filelist.extend(files) 294 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()) 298 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()) 302 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()) 306 307 if self.distribution.has_scripts(): 308 build_scripts = self.get_finalized_command('build_scripts') 309 self.filelist.extend(build_scripts.get_source_files()) 310 311 # add_defaults () 312 313 314 def read_template (self): 315 """Read and parse manifest template file named by self.template. 316 317 (usually "MANIFEST.in") The parsing and processing is done by 318 'self.filelist', which updates itself accordingly. 319 """ 320 log.info("reading manifest template '%s'", self.template) 321 template = TextFile(self.template, 322 strip_comments=1, 323 skip_blanks=1, 324 join_lines=1, 325 lstrip_ws=1, 326 rstrip_ws=1, 327 collapse_join=1) 328 329 while 1: 330 line = template.readline() 331 if line is None: # end of file 332 break 333 334 try: 335 self.filelist.process_template_line(line) 336 except DistutilsTemplateError, msg: 337 self.warn("%s, line %d: %s" % (template.filename, 338 template.current_line, 339 msg)) 340 341 # read_template () 342 343 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 351 """ 352 build = self.get_finalized_command('build') 353 base_dir = self.distribution.get_fullname() 354 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) 358 359 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'. 364 """ 365 self.execute(file_util.write_file, 366 (self.manifest, self.filelist.files), 367 "writing manifest file '%s'" % self.manifest) 368 369 # write_manifest () 370 371 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 375 distribution. 376 """ 377 log.info("reading manifest file '%s'", self.manifest) 378 manifest = open(self.manifest) 379 while 1: 380 line = manifest.readline() 381 if line == '': # end of file 382 break 383 if line[-1] == '\n': 384 line = line[0:-1] 385 self.filelist.append(line) 386 manifest.close() 387 388 # read_manifest () 389 390 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 398 to be distributed. 399 """ 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) 405 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.) 412 413 if hasattr(os, 'link'): # can make hard links on this system 414 link = 'hard' 415 msg = "making hard links in %s..." % base_dir 416 else: # nope, have to copy 417 link = None 418 msg = "copying files to %s..." % base_dir 419 420 if not files: 421 log.warn("no files to distribute -- empty manifest?") 422 else: 423 log.info(msg) 424 for file in files: 425 if not os.path.isfile(file): 426 log.warn("'%s' not a regular file -- skipping" % file) 427 else: 428 dest = os.path.join(base_dir, file) 429 self.copy_file(file, dest, link=link) 430 431 self.distribution.metadata.write_pkg_info(base_dir) 432 433 # make_release_tree () 434 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()'. 442 """ 443 # Don't warn about missing meta-data here -- should be (and is!) 444 # done elsewhere. 445 base_dir = self.distribution.get_fullname() 446 base_name = os.path.join(self.dist_dir, base_dir) 447 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)) 454 455 self.archive_files = archive_files 456 457 if not self.keep_temp: 458 dir_util.remove_tree(base_dir, dry_run=self.dry_run) 459 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. 463 """ 464 return self.archive_files 465 466# class sdist 467