1 """distutils.command.sdist 2 3 Implements the Distutils 'sdist' command (create a source distribution).""" 4 5 __revision__ = "$Id$" 6 7 import os 8 import string 9 import sys 10 from glob import glob 11 from warnings import warn 12 13 from distutils.core import Command 14 from distutils import dir_util, dep_util, file_util, archive_util 15 from distutils.text_file import TextFile 16 from distutils.errors import (DistutilsPlatformError, DistutilsOptionError, 17 DistutilsTemplateError) 18 from distutils.filelist import FileList 19 from distutils import log 20 from distutils.util import convert_path 21 22 def show_formats(): 23 """Print all possible values for the 'formats' option (used by 24 the "--help-formats" command-line option). 25 """ 26 from distutils.fancy_getopt import FancyGetopt 27 from distutils.archive_util import ARCHIVE_FORMATS 28 formats = [] 29 for format in ARCHIVE_FORMATS.keys(): 30 formats.append(("formats=" + format, None, 31 ARCHIVE_FORMATS[format][2])) 32 formats.sort() 33 FancyGetopt(formats).print_help( 34 "List of available source distribution formats:") 35 36 class sdist(Command): 37 38 description = "create a source distribution (tarball, zip file, etc.)" 39 40 def checking_metadata(self): 41 """Callable used for the check sub-command. 42 43 Placed here so user_options can view it""" 44 return self.metadata_check 45 46 user_options = [ 47 ('template=', 't', 48 "name of manifest template file [default: MANIFEST.in]"), 49 ('manifest=', 'm', 50 "name of manifest file [default: MANIFEST]"), 51 ('use-defaults', None, 52 "include the default file set in the manifest " 53 "[default; disable with --no-defaults]"), 54 ('no-defaults', None, 55 "don't include the default file set"), 56 ('prune', None, 57 "specifically exclude files/directories that should not be " 58 "distributed (build tree, RCS/CVS dirs, etc.) " 59 "[default; disable with --no-prune]"), 60 ('no-prune', None, 61 "don't automatically exclude anything"), 62 ('manifest-only', 'o', 63 "just regenerate the manifest and then stop " 64 "(implies --force-manifest)"), 65 ('force-manifest', 'f', 66 "forcibly regenerate the manifest and carry on as usual. " 67 "Deprecated: now the manifest is always regenerated."), 68 ('formats=', None, 69 "formats for source distribution (comma-separated list)"), 70 ('keep-temp', 'k', 71 "keep the distribution tree around after creating " + 72 "archive file(s)"), 73 ('dist-dir=', 'd', 74 "directory to put the source distribution archive(s) in " 75 "[default: dist]"), 76 ('metadata-check', None, 77 "Ensure that all required elements of meta-data " 78 "are supplied. Warn if any missing. [default]"), 79 ('owner=', 'u', 80 "Owner name used when creating a tar file [default: current user]"), 81 ('group=', 'g', 82 "Group name used when creating a tar file [default: current group]"), 83 ] 84 85 boolean_options = ['use-defaults', 'prune', 86 'manifest-only', 'force-manifest', 87 'keep-temp', 'metadata-check'] 88 89 help_options = [ 90 ('help-formats', None, 91 "list available distribution formats", show_formats), 92 ] 93 94 negative_opt = {'no-defaults': 'use-defaults', 95 'no-prune': 'prune' } 96 97 default_format = {'posix': 'gztar', 98 'nt': 'zip' } 99 100 sub_commands = [('check', checking_metadata)] 101 102 def initialize_options(self): 103 # 'template' and 'manifest' are, respectively, the names of 104 # the manifest template and manifest file. 105 self.template = None 106 self.manifest = None 107 108 # 'use_defaults': if true, we will include the default file set 109 # in the manifest 110 self.use_defaults = 1 111 self.prune = 1 112 113 self.manifest_only = 0 114 self.force_manifest = 0 115 116 self.formats = None 117 self.keep_temp = 0 118 self.dist_dir = None 119 120 self.archive_files = None 121 self.metadata_check = 1 122 self.owner = None 123 self.group = None 124 125 def finalize_options(self): 126 if self.manifest is None: 127 self.manifest = "MANIFEST" 128 if self.template is None: 129 self.template = "MANIFEST.in" 130 131 self.ensure_string_list('formats') 132 if self.formats is None: 133 try: 134 self.formats = [self.default_format[os.name]] 135 except KeyError: 136 raise DistutilsPlatformError, \ 137 "don't know how to create source distributions " + \ 138 "on platform %s" % os.name 139 140 bad_format = archive_util.check_archive_formats(self.formats) 141 if bad_format: 142 raise DistutilsOptionError, \ 143 "unknown archive format '%s'" % bad_format 144 145 if self.dist_dir is None: 146 self.dist_dir = "dist" 147 148 def run(self): 149 # 'filelist' contains the list of files that will make up the 150 # manifest 151 self.filelist = FileList() 152 153 # Run sub commands 154 for cmd_name in self.get_sub_commands(): 155 self.run_command(cmd_name) 156 157 # Do whatever it takes to get the list of files to process 158 # (process the manifest template, read an existing manifest, 159 # whatever). File list is accumulated in 'self.filelist'. 160 self.get_file_list() 161 162 # If user just wanted us to regenerate the manifest, stop now. 163 if self.manifest_only: 164 return 165 166 # Otherwise, go ahead and create the source distribution tarball, 167 # or zipfile, or whatever. 168 self.make_distribution() 169 170 def check_metadata(self): 171 """Deprecated API.""" 172 warn("distutils.command.sdist.check_metadata is deprecated, \ 173 use the check command instead", PendingDeprecationWarning) 174 check = self.distribution.get_command_obj('check') 175 check.ensure_finalized() 176 check.run() 177 178 def get_file_list(self): 179 """Figure out the list of files to include in the source 180 distribution, and put it in 'self.filelist'. This might involve 181 reading the manifest template (and writing the manifest), or just 182 reading the manifest, or just using the default file set -- it all 183 depends on the user's options. 184 """ 185 # new behavior: 186 # the file list is recalculated everytime because 187 # even if MANIFEST.in or setup.py are not changed 188 # the user might have added some files in the tree that 189 # need to be included. 190 # 191 # This makes --force the default and only behavior. 192 template_exists = os.path.isfile(self.template) 193 if not template_exists: 194 self.warn(("manifest template '%s' does not exist " + 195 "(using default file list)") % 196 self.template) 197 self.filelist.findall() 198 199 if self.use_defaults: 200 self.add_defaults() 201 202 if template_exists: 203 self.read_template() 204 205 if self.prune: 206 self.prune_file_list() 207 208 self.filelist.sort() 209 self.filelist.remove_duplicates() 210 self.write_manifest() 211 212 def add_defaults(self): 213 """Add all the default files to self.filelist: 214 - README or README.txt 215 - setup.py 216 - test/test*.py 217 - all pure Python modules mentioned in setup script 218 - all files pointed by package_data (build_py) 219 - all files defined in data_files. 220 - all files defined as scripts. 221 - all C sources listed as part of extensions or C libraries 222 in the setup script (doesn't catch C headers!) 223 Warns if (README or README.txt) or setup.py are missing; everything 224 else is optional. 225 """ 226 227 standards = [('README', 'README.txt'), self.distribution.script_name] 228 for fn in standards: 229 if isinstance(fn, tuple): 230 alts = fn 231 got_it = 0 232 for fn in alts: 233 if os.path.exists(fn): 234 got_it = 1 235 self.filelist.append(fn) 236 break 237 238 if not got_it: 239 self.warn("standard file not found: should have one of " + 240 string.join(alts, ', ')) 241 else: 242 if os.path.exists(fn): 243 self.filelist.append(fn) 244 else: 245 self.warn("standard file '%s' not found" % fn) 246 247 optional = ['test/test*.py', 'setup.cfg'] 248 for pattern in optional: 249 files = filter(os.path.isfile, glob(pattern)) 250 if files: 251 self.filelist.extend(files) 252 253 # build_py is used to get: 254 # - python modules 255 # - files defined in package_data 256 build_py = self.get_finalized_command('build_py') 257 258 # getting python files 259 if self.distribution.has_pure_modules(): 260 self.filelist.extend(build_py.get_source_files()) 261 262 # getting package_data files 263 # (computed in build_py.data_files by build_py.finalize_options) 264 for pkg, src_dir, build_dir, filenames in build_py.data_files: 265 for filename in filenames: 266 self.filelist.append(os.path.join(src_dir, filename)) 267 268 # getting distribution.data_files 269 if self.distribution.has_data_files(): 270 for item in self.distribution.data_files: 271 if isinstance(item, str): # plain file 272 item = convert_path(item) 273 if os.path.isfile(item): 274 self.filelist.append(item) 275 else: # a (dirname, filenames) tuple 276 dirname, filenames = item 277 for f in filenames: 278 f = convert_path(f) 279 if os.path.isfile(f): 280 self.filelist.append(f) 281 282 if self.distribution.has_ext_modules(): 283 build_ext = self.get_finalized_command('build_ext') 284 self.filelist.extend(build_ext.get_source_files()) 285 286 if self.distribution.has_c_libraries(): 287 build_clib = self.get_finalized_command('build_clib') 288 self.filelist.extend(build_clib.get_source_files()) 289 290 if self.distribution.has_scripts(): 291 build_scripts = self.get_finalized_command('build_scripts') 292 self.filelist.extend(build_scripts.get_source_files()) 293 294 def read_template(self): 295 """Read and parse manifest template file named by self.template. 296 297 (usually "MANIFEST.in") The parsing and processing is done by 298 'self.filelist', which updates itself accordingly. 299 """ 300 log.info("reading manifest template '%s'", self.template) 301 template = TextFile(self.template, 302 strip_comments=1, 303 skip_blanks=1, 304 join_lines=1, 305 lstrip_ws=1, 306 rstrip_ws=1, 307 collapse_join=1) 308 309 try: 310 while 1: 311 line = template.readline() 312 if line is None: # end of file 313 break 314 315 try: 316 self.filelist.process_template_line(line) 317 except DistutilsTemplateError, msg: 318 self.warn("%s, line %d: %s" % (template.filename, 319 template.current_line, 320 msg)) 321 finally: 322 template.close() 323 324 def prune_file_list(self): 325 """Prune off branches that might slip into the file list as created 326 by 'read_template()', but really don't belong there: 327 * the build tree (typically "build") 328 * the release tree itself (only an issue if we ran "sdist" 329 previously with --keep-temp, or it aborted) 330 * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories 331 """ 332 build = self.get_finalized_command('build') 333 base_dir = self.distribution.get_fullname() 334 335 self.filelist.exclude_pattern(None, prefix=build.build_base) 336 self.filelist.exclude_pattern(None, prefix=base_dir) 337 338 # pruning out vcs directories 339 # both separators are used under win32 340 if sys.platform == 'win32': 341 seps = r'/|\\' 342 else: 343 seps = '/' 344 345 vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', 346 '_darcs'] 347 vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) 348 self.filelist.exclude_pattern(vcs_ptrn, is_regex=1) 349 350 def write_manifest(self): 351 """Write the file list in 'self.filelist' (presumably as filled in 352 by 'add_defaults()' and 'read_template()') to the manifest file 353 named by 'self.manifest'. 354 """ 355 if os.path.isfile(self.manifest): 356 fp = open(self.manifest) 357 try: 358 first_line = fp.readline() 359 finally: 360 fp.close() 361 362 if first_line != '# file GENERATED by distutils, do NOT edit\n': 363 log.info("not writing to manually maintained " 364 "manifest file '%s'" % self.manifest) 365 return 366 367 content = self.filelist.files[:] 368 content.insert(0, '# file GENERATED by distutils, do NOT edit') 369 self.execute(file_util.write_file, (self.manifest, content), 370 "writing manifest file '%s'" % self.manifest) 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 def make_release_tree(self, base_dir, files): 389 """Create the directory tree that will become the source 390 distribution archive. All directories implied by the filenames in 391 'files' are created under 'base_dir', and then we hard link or copy 392 (if hard linking is unavailable) those files into place. 393 Essentially, this duplicates the developer's source tree, but in a 394 directory named after the distribution, containing only the files 395 to be distributed. 396 """ 397 # Create all the directories under 'base_dir' necessary to 398 # put 'files' there; the 'mkpath()' is just so we don't die 399 # if the manifest happens to be empty. 400 self.mkpath(base_dir) 401 dir_util.create_tree(base_dir, files, dry_run=self.dry_run) 402 403 # And walk over the list of files, either making a hard link (if 404 # os.link exists) to each one that doesn't already exist in its 405 # corresponding location under 'base_dir', or copying each file 406 # that's out-of-date in 'base_dir'. (Usually, all files will be 407 # out-of-date, because by default we blow away 'base_dir' when 408 # we're done making the distribution archives.) 409 410 if hasattr(os, 'link'): # can make hard links on this system 411 link = 'hard' 412 msg = "making hard links in %s..." % base_dir 413 else: # nope, have to copy 414 link = None 415 msg = "copying files to %s..." % base_dir 416 417 if not files: 418 log.warn("no files to distribute -- empty manifest?") 419 else: 420 log.info(msg) 421 for file in files: 422 if not os.path.isfile(file): 423 log.warn("'%s' not a regular file -- skipping" % file) 424 else: 425 dest = os.path.join(base_dir, file) 426 self.copy_file(file, dest, link=link) 427 428 self.distribution.metadata.write_pkg_info(base_dir) 429 430 def make_distribution(self): 431 """Create the source distribution(s). First, we create the release 432 tree with 'make_release_tree()'; then, we create all required 433 archive files (according to 'self.formats') from the release tree. 434 Finally, we clean up by blowing away the release tree (unless 435 'self.keep_temp' is true). The list of archive files created is 436 stored so it can be retrieved later by 'get_archive_files()'. 437 """ 438 # Don't warn about missing meta-data here -- should be (and is!) 439 # done elsewhere. 440 base_dir = self.distribution.get_fullname() 441 base_name = os.path.join(self.dist_dir, base_dir) 442 443 self.make_release_tree(base_dir, self.filelist.files) 444 archive_files = [] # remember names of files we create 445 # tar archive must be created last to avoid overwrite and remove 446 if 'tar' in self.formats: 447 self.formats.append(self.formats.pop(self.formats.index('tar'))) 448 449 for fmt in self.formats: 450 file = self.make_archive(base_name, fmt, base_dir=base_dir, 451 owner=self.owner, group=self.group) 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