/Lib/distutils/command/sdist.py
Python | 481 lines | 479 code | 0 blank | 2 comment | 11 complexity | 0434dbed50961be06c51a804402abc89 MD5 | raw file
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: sdist.py 68968 2009-01-26 17:20:15Z tarek.ziade $" 8 9import os, string 10import sys 11from types import * 12from glob import glob 13from distutils.core import Command 14from distutils import dir_util, dep_util, file_util, archive_util 15from distutils.text_file import TextFile 16from distutils.errors import * 17from distutils.filelist import FileList 18from distutils import log 19 20 21def show_formats (): 22 """Print all possible values for the 'formats' option (used by 23 the "--help-formats" command-line option). 24 """ 25 from distutils.fancy_getopt import FancyGetopt 26 from distutils.archive_util import ARCHIVE_FORMATS 27 formats=[] 28 for format in ARCHIVE_FORMATS.keys(): 29 formats.append(("formats=" + format, None, 30 ARCHIVE_FORMATS[format][2])) 31 formats.sort() 32 pretty_printer = FancyGetopt(formats) 33 pretty_printer.print_help( 34 "List of available source distribution formats:") 35 36class sdist (Command): 37 38 description = "create a source distribution (tarball, zip file, etc.)" 39 40 user_options = [ 41 ('template=', 't', 42 "name of manifest template file [default: MANIFEST.in]"), 43 ('manifest=', 'm', 44 "name of manifest file [default: MANIFEST]"), 45 ('use-defaults', None, 46 "include the default file set in the manifest " 47 "[default; disable with --no-defaults]"), 48 ('no-defaults', None, 49 "don't include the default file set"), 50 ('prune', None, 51 "specifically exclude files/directories that should not be " 52 "distributed (build tree, RCS/CVS dirs, etc.) " 53 "[default; disable with --no-prune]"), 54 ('no-prune', None, 55 "don't automatically exclude anything"), 56 ('manifest-only', 'o', 57 "just regenerate the manifest and then stop " 58 "(implies --force-manifest)"), 59 ('force-manifest', 'f', 60 "forcibly regenerate the manifest and carry on as usual"), 61 ('formats=', None, 62 "formats for source distribution (comma-separated list)"), 63 ('keep-temp', 'k', 64 "keep the distribution tree around after creating " + 65 "archive file(s)"), 66 ('dist-dir=', 'd', 67 "directory to put the source distribution archive(s) in " 68 "[default: dist]"), 69 ] 70 71 boolean_options = ['use-defaults', 'prune', 72 'manifest-only', 'force-manifest', 73 'keep-temp'] 74 75 help_options = [ 76 ('help-formats', None, 77 "list available distribution formats", show_formats), 78 ] 79 80 negative_opt = {'no-defaults': 'use-defaults', 81 'no-prune': 'prune' } 82 83 default_format = { 'posix': 'gztar', 84 'nt': 'zip' } 85 86 def initialize_options (self): 87 # 'template' and 'manifest' are, respectively, the names of 88 # the manifest template and manifest file. 89 self.template = None 90 self.manifest = None 91 92 # 'use_defaults': if true, we will include the default file set 93 # in the manifest 94 self.use_defaults = 1 95 self.prune = 1 96 97 self.manifest_only = 0 98 self.force_manifest = 0 99 100 self.formats = None 101 self.keep_temp = 0 102 self.dist_dir = None 103 104 self.archive_files = None 105 106 107 def finalize_options (self): 108 if self.manifest is None: 109 self.manifest = "MANIFEST" 110 if self.template is None: 111 self.template = "MANIFEST.in" 112 113 self.ensure_string_list('formats') 114 if self.formats is None: 115 try: 116 self.formats = [self.default_format[os.name]] 117 except KeyError: 118 raise DistutilsPlatformError, \ 119 "don't know how to create source distributions " + \ 120 "on platform %s" % os.name 121 122 bad_format = archive_util.check_archive_formats(self.formats) 123 if bad_format: 124 raise DistutilsOptionError, \ 125 "unknown archive format '%s'" % bad_format 126 127 if self.dist_dir is None: 128 self.dist_dir = "dist" 129 130 131 def run (self): 132 133 # 'filelist' contains the list of files that will make up the 134 # manifest 135 self.filelist = FileList() 136 137 # Ensure that all required meta-data is given; warn if not (but 138 # don't die, it's not *that* serious!) 139 self.check_metadata() 140 141 # Do whatever it takes to get the list of files to process 142 # (process the manifest template, read an existing manifest, 143 # whatever). File list is accumulated in 'self.filelist'. 144 self.get_file_list() 145 146 # If user just wanted us to regenerate the manifest, stop now. 147 if self.manifest_only: 148 return 149 150 # Otherwise, go ahead and create the source distribution tarball, 151 # or zipfile, or whatever. 152 self.make_distribution() 153 154 155 def check_metadata (self): 156 """Ensure that all required elements of meta-data (name, version, 157 URL, (author and author_email) or (maintainer and 158 maintainer_email)) are supplied by the Distribution object; warn if 159 any are missing. 160 """ 161 metadata = self.distribution.metadata 162 163 missing = [] 164 for attr in ('name', 'version', 'url'): 165 if not (hasattr(metadata, attr) and getattr(metadata, attr)): 166 missing.append(attr) 167 168 if missing: 169 self.warn("missing required meta-data: " + 170 string.join(missing, ", ")) 171 172 if metadata.author: 173 if not metadata.author_email: 174 self.warn("missing meta-data: if 'author' supplied, " + 175 "'author_email' must be supplied too") 176 elif metadata.maintainer: 177 if not metadata.maintainer_email: 178 self.warn("missing meta-data: if 'maintainer' supplied, " + 179 "'maintainer_email' must be supplied too") 180 else: 181 self.warn("missing meta-data: either (author and author_email) " + 182 "or (maintainer and maintainer_email) " + 183 "must be supplied") 184 185 # check_metadata () 186 187 188 def get_file_list (self): 189 """Figure out the list of files to include in the source 190 distribution, and put it in 'self.filelist'. This might involve 191 reading the manifest template (and writing the manifest), or just 192 reading the manifest, or just using the default file set -- it all 193 depends on the user's options and the state of the filesystem. 194 """ 195 196 # If we have a manifest template, see if it's newer than the 197 # manifest; if so, we'll regenerate the manifest. 198 template_exists = os.path.isfile(self.template) 199 if template_exists: 200 template_newer = dep_util.newer(self.template, self.manifest) 201 202 # The contents of the manifest file almost certainly depend on the 203 # setup script as well as the manifest template -- so if the setup 204 # script is newer than the manifest, we'll regenerate the manifest 205 # from the template. (Well, not quite: if we already have a 206 # manifest, but there's no template -- which will happen if the 207 # developer elects to generate a manifest some other way -- then we 208 # can't regenerate the manifest, so we don't.) 209 self.debug_print("checking if %s newer than %s" % 210 (self.distribution.script_name, self.manifest)) 211 setup_newer = dep_util.newer(self.distribution.script_name, 212 self.manifest) 213 214 # cases: 215 # 1) no manifest, template exists: generate manifest 216 # (covered by 2a: no manifest == template newer) 217 # 2) manifest & template exist: 218 # 2a) template or setup script newer than manifest: 219 # regenerate manifest 220 # 2b) manifest newer than both: 221 # do nothing (unless --force or --manifest-only) 222 # 3) manifest exists, no template: 223 # do nothing (unless --force or --manifest-only) 224 # 4) no manifest, no template: generate w/ warning ("defaults only") 225 226 manifest_outofdate = (template_exists and 227 (template_newer or setup_newer)) 228 force_regen = self.force_manifest or self.manifest_only 229 manifest_exists = os.path.isfile(self.manifest) 230 neither_exists = (not template_exists and not manifest_exists) 231 232 # Regenerate the manifest if necessary (or if explicitly told to) 233 if manifest_outofdate or neither_exists or force_regen: 234 if not template_exists: 235 self.warn(("manifest template '%s' does not exist " + 236 "(using default file list)") % 237 self.template) 238 self.filelist.findall() 239 240 if self.use_defaults: 241 self.add_defaults() 242 if template_exists: 243 self.read_template() 244 if self.prune: 245 self.prune_file_list() 246 247 self.filelist.sort() 248 self.filelist.remove_duplicates() 249 self.write_manifest() 250 251 # Don't regenerate the manifest, just read it in. 252 else: 253 self.read_manifest() 254 255 # get_file_list () 256 257 258 def add_defaults (self): 259 """Add all the default files to self.filelist: 260 - README or README.txt 261 - setup.py 262 - test/test*.py 263 - all pure Python modules mentioned in setup script 264 - all C sources listed as part of extensions or C libraries 265 in the setup script (doesn't catch C headers!) 266 Warns if (README or README.txt) or setup.py are missing; everything 267 else is optional. 268 """ 269 270 standards = [('README', 'README.txt'), self.distribution.script_name] 271 for fn in standards: 272 if type(fn) is TupleType: 273 alts = fn 274 got_it = 0 275 for fn in alts: 276 if os.path.exists(fn): 277 got_it = 1 278 self.filelist.append(fn) 279 break 280 281 if not got_it: 282 self.warn("standard file not found: should have one of " + 283 string.join(alts, ', ')) 284 else: 285 if os.path.exists(fn): 286 self.filelist.append(fn) 287 else: 288 self.warn("standard file '%s' not found" % fn) 289 290 optional = ['test/test*.py', 'setup.cfg'] 291 for pattern in optional: 292 files = filter(os.path.isfile, glob(pattern)) 293 if files: 294 self.filelist.extend(files) 295 296 if self.distribution.has_pure_modules(): 297 build_py = self.get_finalized_command('build_py') 298 self.filelist.extend(build_py.get_source_files()) 299 300 if self.distribution.has_ext_modules(): 301 build_ext = self.get_finalized_command('build_ext') 302 self.filelist.extend(build_ext.get_source_files()) 303 304 if self.distribution.has_c_libraries(): 305 build_clib = self.get_finalized_command('build_clib') 306 self.filelist.extend(build_clib.get_source_files()) 307 308 if self.distribution.has_scripts(): 309 build_scripts = self.get_finalized_command('build_scripts') 310 self.filelist.extend(build_scripts.get_source_files()) 311 312 # add_defaults () 313 314 315 def read_template (self): 316 """Read and parse manifest template file named by self.template. 317 318 (usually "MANIFEST.in") The parsing and processing is done by 319 'self.filelist', which updates itself accordingly. 320 """ 321 log.info("reading manifest template '%s'", self.template) 322 template = TextFile(self.template, 323 strip_comments=1, 324 skip_blanks=1, 325 join_lines=1, 326 lstrip_ws=1, 327 rstrip_ws=1, 328 collapse_join=1) 329 330 while 1: 331 line = template.readline() 332 if line is None: # end of file 333 break 334 335 try: 336 self.filelist.process_template_line(line) 337 except DistutilsTemplateError, msg: 338 self.warn("%s, line %d: %s" % (template.filename, 339 template.current_line, 340 msg)) 341 342 # read_template () 343 344 345 def prune_file_list (self): 346 """Prune off branches that might slip into the file list as created 347 by 'read_template()', but really don't belong there: 348 * the build tree (typically "build") 349 * the release tree itself (only an issue if we ran "sdist" 350 previously with --keep-temp, or it aborted) 351 * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories 352 """ 353 build = self.get_finalized_command('build') 354 base_dir = self.distribution.get_fullname() 355 356 self.filelist.exclude_pattern(None, prefix=build.build_base) 357 self.filelist.exclude_pattern(None, prefix=base_dir) 358 359 # pruning out vcs directories 360 # both separators are used under win32 361 if sys.platform == 'win32': 362 seps = r'/|\\' 363 else: 364 seps = '/' 365 366 vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', 367 '_darcs'] 368 vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) 369 self.filelist.exclude_pattern(vcs_ptrn, is_regex=1) 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 log.info("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 manifest.close() 398 399 # read_manifest () 400 401 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 409 to be distributed. 410 """ 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) 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 log.warn("no files to distribute -- empty manifest?") 433 else: 434 log.info(msg) 435 for file in files: 436 if not os.path.isfile(file): 437 log.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 self.distribution.metadata.write_pkg_info(base_dir) 443 444 # make_release_tree () 445 446 def make_distribution (self): 447 """Create the source distribution(s). First, we create the release 448 tree with 'make_release_tree()'; then, we create all required 449 archive files (according to 'self.formats') from the release tree. 450 Finally, we clean up by blowing away the release tree (unless 451 'self.keep_temp' is true). The list of archive files created is 452 stored so it can be retrieved later by 'get_archive_files()'. 453 """ 454 # Don't warn about missing meta-data here -- should be (and is!) 455 # done elsewhere. 456 base_dir = self.distribution.get_fullname() 457 base_name = os.path.join(self.dist_dir, base_dir) 458 459 self.make_release_tree(base_dir, self.filelist.files) 460 archive_files = [] # remember names of files we create 461 # tar archive must be created last to avoid overwrite and remove 462 if 'tar' in self.formats: 463 self.formats.append(self.formats.pop(self.formats.index('tar'))) 464 465 for fmt in self.formats: 466 file = self.make_archive(base_name, fmt, base_dir=base_dir) 467 archive_files.append(file) 468 self.distribution.dist_files.append(('sdist', '', file)) 469 470 self.archive_files = archive_files 471 472 if not self.keep_temp: 473 dir_util.remove_tree(base_dir, dry_run=self.dry_run) 474 475 def get_archive_files (self): 476 """Return the list of archive files created when the command 477 was run, or None if the command hasn't run yet. 478 """ 479 return self.archive_files 480 481# class sdist