sdist.py revision fa9ff76aae32ecaae6b5409a410c10ff12116732
1"""distutils.command.sdist 2 3Implements the Distutils 'sdist' command (create a source distribution).""" 4 5# created 1999/09/22, Greg Ward 6 7__revision__ = "$Id$" 8 9import sys, 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 17 18 19def show_formats (): 20 """Print all possible values for the 'formats' option (used by 21 the "--help-formats" command-line option). 22 """ 23 from distutils.fancy_getopt import FancyGetopt 24 from distutils.archive_util import ARCHIVE_FORMATS 25 formats=[] 26 for format in ARCHIVE_FORMATS.keys(): 27 formats.append(("formats=" + format, None, 28 ARCHIVE_FORMATS[format][2])) 29 formats.sort() 30 pretty_printer = FancyGetopt(formats) 31 pretty_printer.print_help( 32 "List of available source distribution formats:") 33 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 238 self.filelist.findall() 239 240 # Add default file set to 'files' 241 if self.use_defaults: 242 self.add_defaults() 243 244 # Read manifest template if it exists 245 if template_exists: 246 self.read_template() 247 248 # Prune away any directories that don't belong in the source 249 # distribution 250 if self.prune: 251 self.prune_file_list() 252 253 # File list now complete -- sort it so that higher-level files 254 # come first 255 self.filelist.sort() 256 257 # Remove duplicates from the file list 258 self.filelist.remove_duplicates() 259 260 # And write complete file list (including default file set) to 261 # the manifest. 262 self.write_manifest() 263 264 # Don't regenerate the manifest, just read it in. 265 else: 266 self.read_manifest() 267 268 # get_file_list () 269 270 271 def add_defaults (self): 272 """Add all the default files to self.filelist: 273 - README or README.txt 274 - setup.py 275 - test/test*.py 276 - all pure Python modules mentioned in setup script 277 - all C sources listed as part of extensions or C libraries 278 in the setup script (doesn't catch C headers!) 279 Warns if (README or README.txt) or setup.py are missing; everything 280 else is optional. 281 """ 282 283 standards = [('README', 'README.txt'), self.distribution.script_name] 284 for fn in standards: 285 if type(fn) is TupleType: 286 alts = fn 287 got_it = 0 288 for fn in alts: 289 if os.path.exists(fn): 290 got_it = 1 291 self.filelist.append(fn) 292 break 293 294 if not got_it: 295 self.warn("standard file not found: should have one of " + 296 string.join(alts, ', ')) 297 else: 298 if os.path.exists(fn): 299 self.filelist.append(fn) 300 else: 301 self.warn("standard file '%s' not found" % fn) 302 303 optional = ['test/test*.py', 'setup.cfg'] 304 for pattern in optional: 305 files = filter(os.path.isfile, glob(pattern)) 306 if files: 307 self.filelist.extend(files) 308 309 if self.distribution.has_pure_modules(): 310 build_py = self.get_finalized_command('build_py') 311 self.filelist.extend(build_py.get_source_files()) 312 313 if self.distribution.has_ext_modules(): 314 build_ext = self.get_finalized_command('build_ext') 315 self.filelist.extend(build_ext.get_source_files()) 316 317 if self.distribution.has_c_libraries(): 318 build_clib = self.get_finalized_command('build_clib') 319 self.filelist.extend(build_clib.get_source_files()) 320 321 # add_defaults () 322 323 324 def read_template (self): 325 326 """Read and parse the manifest template file named by 327 'self.template' (usually "MANIFEST.in"). The parsing and 328 processing is done by 'self.filelist', which updates itself 329 accordingly. 330 """ 331 self.announce("reading manifest template '%s'" % self.template) 332 template = TextFile(self.template, 333 strip_comments=1, 334 skip_blanks=1, 335 join_lines=1, 336 lstrip_ws=1, 337 rstrip_ws=1, 338 collapse_join=1) 339 340 while 1: 341 line = template.readline() 342 if line is None: # end of file 343 break 344 345 try: 346 self.filelist.process_template_line(line) 347 except DistutilsTemplateError, msg: 348 self.warn("%s, line %d: %s" % (template.filename, 349 template.current_line, 350 msg)) 351 352 # read_template () 353 354 355 def prune_file_list (self): 356 """Prune off branches that might slip into the file list as created 357 by 'read_template()', but really don't belong there: 358 * the build tree (typically "build") 359 * the release tree itself (only an issue if we ran "sdist" 360 previously with --keep-temp, or it aborted) 361 * any RCS or CVS directories 362 """ 363 build = self.get_finalized_command('build') 364 base_dir = self.distribution.get_fullname() 365 366 self.filelist.exclude_pattern(None, prefix=build.build_base) 367 self.filelist.exclude_pattern(None, prefix=base_dir) 368 self.filelist.exclude_pattern(r'/(RCS|CVS)/.*', is_regex=1) 369 370 371 def write_manifest (self): 372 """Write the file list in 'self.filelist' (presumably as filled in 373 by 'add_defaults()' and 'read_template()') to the manifest file 374 named by 'self.manifest'. 375 """ 376 self.execute(file_util.write_file, 377 (self.manifest, self.filelist.files), 378 "writing manifest file '%s'" % self.manifest) 379 380 # write_manifest () 381 382 383 def read_manifest (self): 384 """Read the manifest file (named by 'self.manifest') and use it to 385 fill in 'self.filelist', the list of files to include in the source 386 distribution. 387 """ 388 self.announce("reading manifest file '%s'" % self.manifest) 389 manifest = open(self.manifest) 390 while 1: 391 line = manifest.readline() 392 if line == '': # end of file 393 break 394 if line[-1] == '\n': 395 line = line[0:-1] 396 self.filelist.append(line) 397 398 # read_manifest () 399 400 401 def make_release_tree (self, base_dir, files): 402 """Create the directory tree that will become the source 403 distribution archive. All directories implied by the filenames in 404 'files' are created under 'base_dir', and then we hard link or copy 405 (if hard linking is unavailable) those files into place. 406 Essentially, this duplicates the developer's source tree, but in a 407 directory named after the distribution, containing only the files 408 to be distributed. 409 """ 410 # Create all the directories under 'base_dir' necessary to 411 # put 'files' there; the 'mkpath()' is just so we don't die 412 # if the manifest happens to be empty. 413 self.mkpath(base_dir) 414 dir_util.create_tree(base_dir, files, 415 verbose=self.verbose, 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 self.warn("no files to distribute -- empty manifest?") 433 else: 434 self.announce(msg) 435 for file in files: 436 if not os.path.isfile(file): 437 self.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 # make_release_tree () 443 444 445 def make_distribution (self): 446 """Create the source distribution(s). First, we create the release 447 tree with 'make_release_tree()'; then, we create all required 448 archive files (according to 'self.formats') from the release tree. 449 Finally, we clean up by blowing away the release tree (unless 450 'self.keep_temp' is true). The list of archive files created is 451 stored so it can be retrieved later by 'get_archive_files()'. 452 """ 453 # Don't warn about missing meta-data here -- should be (and is!) 454 # done elsewhere. 455 base_dir = self.distribution.get_fullname() 456 base_name = os.path.join(self.dist_dir, base_dir) 457 458 self.make_release_tree(base_dir, self.filelist.files) 459 archive_files = [] # remember names of files we create 460 for fmt in self.formats: 461 file = self.make_archive(base_name, fmt, base_dir=base_dir) 462 archive_files.append(file) 463 464 self.archive_files = archive_files 465 466 if not self.keep_temp: 467 dir_util.remove_tree(base_dir, self.verbose, self.dry_run) 468 469 def get_archive_files (self): 470 """Return the list of archive files created when the command 471 was run, or None if the command hasn't run yet. 472 """ 473 return self.archive_files 474 475# class sdist 476