sdist.py revision 9821bf4e62bcb7d503aed782a8f6398e5de720af
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-tree', '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 71 help_options = [ 72 ('help-formats', None, 73 "list available distribution formats", show_formats), 74 ] 75 76 negative_opt = {'no-defaults': 'use-defaults', 77 'no-prune': 'prune' } 78 79 default_format = { 'posix': 'gztar', 80 'nt': 'zip' } 81 82 def initialize_options (self): 83 # 'template' and 'manifest' are, respectively, the names of 84 # the manifest template and manifest file. 85 self.template = None 86 self.manifest = None 87 88 # 'use_defaults': if true, we will include the default file set 89 # in the manifest 90 self.use_defaults = 1 91 self.prune = 1 92 93 self.manifest_only = 0 94 self.force_manifest = 0 95 96 self.formats = None 97 self.keep_tree = 0 98 self.dist_dir = None 99 100 self.archive_files = None 101 102 103 def finalize_options (self): 104 if self.manifest is None: 105 self.manifest = "MANIFEST" 106 if self.template is None: 107 self.template = "MANIFEST.in" 108 109 self.ensure_string_list('formats') 110 if self.formats is None: 111 try: 112 self.formats = [self.default_format[os.name]] 113 except KeyError: 114 raise DistutilsPlatformError, \ 115 "don't know how to create source distributions " + \ 116 "on platform %s" % os.name 117 118 bad_format = archive_util.check_archive_formats (self.formats) 119 if bad_format: 120 raise DistutilsOptionError, \ 121 "unknown archive format '%s'" % bad_format 122 123 if self.dist_dir is None: 124 self.dist_dir = "dist" 125 126 127 def run (self): 128 129 # 'filelist' contains the list of files that will make up the 130 # manifest 131 self.filelist = FileList() 132 133 # Ensure that all required meta-data is given; warn if not (but 134 # don't die, it's not *that* serious!) 135 self.check_metadata () 136 137 # Do whatever it takes to get the list of files to process 138 # (process the manifest template, read an existing manifest, 139 # whatever). File list is accumulated in 'self.filelist'. 140 self.get_file_list () 141 142 # If user just wanted us to regenerate the manifest, stop now. 143 if self.manifest_only: 144 return 145 146 # Otherwise, go ahead and create the source distribution tarball, 147 # or zipfile, or whatever. 148 self.make_distribution () 149 150 151 def check_metadata (self): 152 """Ensure that all required elements of meta-data (name, version, 153 URL, (author and author_email) or (maintainer and 154 maintainer_email)) are supplied by the Distribution object; warn if 155 any are missing. 156 """ 157 metadata = self.distribution.metadata 158 159 missing = [] 160 for attr in ('name', 'version', 'url'): 161 if not (hasattr (metadata, attr) and getattr (metadata, attr)): 162 missing.append (attr) 163 164 if missing: 165 self.warn ("missing required meta-data: " + 166 string.join (missing, ", ")) 167 168 if metadata.author: 169 if not metadata.author_email: 170 self.warn ("missing meta-data: if 'author' supplied, " + 171 "'author_email' must be supplied too") 172 elif metadata.maintainer: 173 if not metadata.maintainer_email: 174 self.warn ("missing meta-data: if 'maintainer' supplied, " + 175 "'maintainer_email' must be supplied too") 176 else: 177 self.warn ("missing meta-data: either (author and author_email) " + 178 "or (maintainer and maintainer_email) " + 179 "must be supplied") 180 181 # check_metadata () 182 183 184 def get_file_list (self): 185 """Figure out the list of files to include in the source 186 distribution, and put it in 'self.filelist'. This might involve 187 reading the manifest template (and writing the manifest), or just 188 reading the manifest, or just using the default file set -- it all 189 depends on the user's options and the state of the filesystem. 190 """ 191 192 # If we have a manifest template, see if it's newer than the 193 # manifest; if so, we'll regenerate the manifest. 194 template_exists = os.path.isfile(self.template) 195 if template_exists: 196 template_newer = dep_util.newer(self.template, self.manifest) 197 198 # The contents of the manifest file almost certainly depend on the 199 # setup script as well as the manifest template -- so if the setup 200 # script is newer than the manifest, we'll regenerate the manifest 201 # from the template. (Well, not quite: if we already have a 202 # manifest, but there's no template -- which will happen if the 203 # developer elects to generate a manifest some other way -- then we 204 # can't regenerate the manifest, so we don't.) 205 self.debug_print("checking if %s newer than %s" % 206 (self.distribution.script_name, self.manifest)) 207 setup_newer = dep_util.newer(self.distribution.script_name, 208 self.manifest) 209 210 # cases: 211 # 1) no manifest, template exists: generate manifest 212 # (covered by 2a: no manifest == template newer) 213 # 2) manifest & template exist: 214 # 2a) template or setup script newer than manifest: 215 # regenerate manifest 216 # 2b) manifest newer than both: 217 # do nothing (unless --force or --manifest-only) 218 # 3) manifest exists, no template: 219 # do nothing (unless --force or --manifest-only) 220 # 4) no manifest, no template: generate w/ warning ("defaults only") 221 222 # Regenerate the manifest if necessary (or if explicitly told to) 223 if ((template_exists and (template_newer or setup_newer)) or 224 self.force_manifest or self.manifest_only): 225 226 if not template_exists: 227 self.warn(("manifest template '%s' does not exist " + 228 "(using default file list)") % 229 self.template) 230 231 self.filelist.findall() 232 233 # Add default file set to 'files' 234 if self.use_defaults: 235 self.add_defaults() 236 237 # Read manifest template if it exists 238 if template_exists: 239 self.read_template() 240 241 # Prune away any directories that don't belong in the source 242 # distribution 243 if self.prune: 244 self.prune_file_list() 245 246 # File list now complete -- sort it so that higher-level files 247 # come first 248 self.filelist.sort() 249 250 # Remove duplicates from the file list 251 self.filelist.remove_duplicates() 252 253 # And write complete file list (including default file set) to 254 # the manifest. 255 self.write_manifest() 256 257 # Don't regenerate the manifest, just read it in. 258 else: 259 self.read_manifest() 260 261 # get_file_list () 262 263 264 def add_defaults (self): 265 """Add all the default files to self.filelist: 266 - README or README.txt 267 - setup.py 268 - test/test*.py 269 - all pure Python modules mentioned in setup script 270 - all C sources listed as part of extensions or C libraries 271 in the setup script (doesn't catch C headers!) 272 Warns if (README or README.txt) or setup.py are missing; everything 273 else is optional. 274 """ 275 276 # XXX name of setup script and config file should be taken 277 # programmatically from the Distribution object (except 278 # it doesn't have that capability... yet!) 279 standards = [('README', 'README.txt'), 'setup.py'] 280 for fn in standards: 281 if type (fn) is TupleType: 282 alts = fn 283 got_it = 0 284 for fn in alts: 285 if os.path.exists (fn): 286 got_it = 1 287 self.filelist.append (fn) 288 break 289 290 if not got_it: 291 self.warn ("standard file not found: should have one of " + 292 string.join (alts, ', ')) 293 else: 294 if os.path.exists (fn): 295 self.filelist.append (fn) 296 else: 297 self.warn ("standard file '%s' not found" % fn) 298 299 optional = ['test/test*.py', 'setup.cfg'] 300 for pattern in optional: 301 files = filter (os.path.isfile, glob (pattern)) 302 if files: 303 self.filelist.extend (files) 304 305 if self.distribution.has_pure_modules(): 306 build_py = self.get_finalized_command ('build_py') 307 self.filelist.extend (build_py.get_source_files ()) 308 309 if self.distribution.has_ext_modules(): 310 build_ext = self.get_finalized_command ('build_ext') 311 self.filelist.extend (build_ext.get_source_files ()) 312 313 if self.distribution.has_c_libraries(): 314 build_clib = self.get_finalized_command ('build_clib') 315 self.filelist.extend (build_clib.get_source_files ()) 316 317 # add_defaults () 318 319 320 def read_template (self): 321 322 """Read and parse the manifest template file named by 323 'self.template' (usually "MANIFEST.in"). The parsing and 324 processing is done by 'self.filelist', which updates itself 325 accordingly. 326 """ 327 self.announce("reading manifest template '%s'" % self.template) 328 template = TextFile (self.template, 329 strip_comments=1, 330 skip_blanks=1, 331 join_lines=1, 332 lstrip_ws=1, 333 rstrip_ws=1, 334 collapse_ws=1) 335 336 while 1: 337 line = template.readline() 338 if line is None: # end of file 339 break 340 341 try: 342 self.filelist.process_template_line(line) 343 except DistutilsTemplateError, msg: 344 self.warn("%s, line %d: %s" % (template.filename, 345 template.current_line, 346 msg)) 347 348 # read_template () 349 350 351 def prune_file_list (self): 352 """Prune off branches that might slip into the file list as created 353 by 'read_template()', but really don't belong there: 354 * the build tree (typically "build") 355 * the release tree itself (only an issue if we ran "sdist" 356 previously with --keep-tree, or it aborted) 357 * any RCS or CVS directories 358 """ 359 build = self.get_finalized_command('build') 360 base_dir = self.distribution.get_fullname() 361 362 self.filelist.exclude_pattern(None, prefix=build.build_base) 363 self.filelist.exclude_pattern(None, prefix=base_dir) 364 self.filelist.exclude_pattern(r'/(RCS|CVS)/.*', is_regex=1) 365 366 367 def write_manifest (self): 368 """Write the file list in 'self.filelist' (presumably as filled in 369 by 'add_defaults()' and 'read_template()') to the manifest file 370 named by 'self.manifest'. 371 """ 372 self.execute(file_util.write_file, 373 (self.manifest, self.filelist.files), 374 "writing manifest file '%s'" % self.manifest) 375 376 # write_manifest () 377 378 379 def read_manifest (self): 380 """Read the manifest file (named by 'self.manifest') and use it to 381 fill in 'self.filelist', the list of files to include in the source 382 distribution. 383 """ 384 self.announce("reading manifest file '%s'" % self.manifest) 385 manifest = open (self.manifest) 386 while 1: 387 line = manifest.readline () 388 if line == '': # end of file 389 break 390 if line[-1] == '\n': 391 line = line[0:-1] 392 self.filelist.append (line) 393 394 # read_manifest () 395 396 397 def make_release_tree (self, base_dir, files): 398 """Create the directory tree that will become the source 399 distribution archive. All directories implied by the filenames in 400 'files' are created under 'base_dir', and then we hard link or copy 401 (if hard linking is unavailable) those files into place. 402 Essentially, this duplicates the developer's source tree, but in a 403 directory named after the distribution, containing only the files 404 to be distributed. 405 """ 406 # Create all the directories under 'base_dir' necessary to 407 # put 'files' there. 408 dir_util.create_tree (base_dir, files, 409 verbose=self.verbose, dry_run=self.dry_run) 410 411 # And walk over the list of files, either making a hard link (if 412 # os.link exists) to each one that doesn't already exist in its 413 # corresponding location under 'base_dir', or copying each file 414 # that's out-of-date in 'base_dir'. (Usually, all files will be 415 # out-of-date, because by default we blow away 'base_dir' when 416 # we're done making the distribution archives.) 417 418 if hasattr (os, 'link'): # can make hard links on this system 419 link = 'hard' 420 msg = "making hard links in %s..." % base_dir 421 else: # nope, have to copy 422 link = None 423 msg = "copying files to %s..." % base_dir 424 425 self.announce (msg) 426 for file in files: 427 dest = os.path.join (base_dir, file) 428 self.copy_file (file, dest, link=link) 429 430 # make_release_tree () 431 432 433 def make_distribution (self): 434 """Create the source distribution(s). First, we create the release 435 tree with 'make_release_tree()'; then, we create all required 436 archive files (according to 'self.formats') from the release tree. 437 Finally, we clean up by blowing away the release tree (unless 438 'self.keep_tree' is true). The list of archive files created is 439 stored so it can be retrieved later by 'get_archive_files()'. 440 """ 441 # Don't warn about missing meta-data here -- should be (and is!) 442 # done elsewhere. 443 base_dir = self.distribution.get_fullname() 444 base_name = os.path.join(self.dist_dir, base_dir) 445 446 self.make_release_tree (base_dir, self.filelist.files) 447 archive_files = [] # remember names of files we create 448 for fmt in self.formats: 449 file = self.make_archive (base_name, fmt, base_dir=base_dir) 450 archive_files.append(file) 451 452 self.archive_files = archive_files 453 454 if not self.keep_tree: 455 dir_util.remove_tree (base_dir, self.verbose, self.dry_run) 456 457 def get_archive_files (self): 458 """Return the list of archive files created when the command 459 was run, or None if the command hasn't run yet. 460 """ 461 return self.archive_files 462 463# class sdist 464