import os from distutils import dir_util, filelist, util from distutils.command import sdist class SDist(sdist.sdist): """ Extended 'sdist' command that uses 'manifest_templates' from the distribution options instead of manifest files. """ command_name = 'sdist' description = "create a source distribution (tarball, zip file, etc.)" user_options = [ ('use-defaults', None, "include the default file set in the manifest " "[default; disable with --no-defaults]"), ('no-defaults', None, "don't include the default file set"), ('prune', None, "specifically exclude files/directories that should not be " "distributed (build tree, RCS/CVS dirs, etc.) " "[default; disable with --no-prune]"), ('no-prune', None, "don't automatically exclude anything"), ('formats=', None, "formats for source distribution (comma-separated list)"), ('keep-temp', 'k', "keep the distribution tree around after creating " + "archive file(s)"), ('dist-dir=', 'd', "directory to put the source distribution archive(s) in " "[default: dist]"), ] # sdist.sdist defines this, so we must undefine it negative_opt = {} def get_file_list(self): """ Figure out the list of files to include in the source distribution, and put it in 'self.filelist'. """ self.filelist.set_allfiles(self.distribution.get_allfiles()) # Add default file set to 'files' if self.use_defaults: self.add_defaults() # Process manifest template lines if not self.distribution.manifest_templates: self.announce("using default file list only", 1) else: for line in self.distribution.manifest_templates: try: self.filelist.process_template_line(line) except DistutilsTemplateError, msg: self.warn(str(msg)) # Prune away any directories that don't belong in the source # distribution if self.prune: self.prune_file_list(self.filelist) # File list now complete -- sort it so that higher-level files # come first self.filelist.sort() # Remove duplicates from the file list self.filelist.remove_duplicates() return def add_defaults(self): """Add all the default files to self.filelist: - setup.py - README or README.txt (in all directories) - all pure Python modules mentioned in setup script - all C sources listed as part of extensions or C libraries in the setup script (doesn't catch C headers!) """ self.filelist.include_pattern('README*') self.filelist.include_pattern('COPYRIGHT') # Add the files from the distribution self.filelist.extend(self.distribution.get_source_files()) return def prune_file_list(self, filelist): """Prune off branches that might slip into the file list as created by 'read_template()', but really don't belong there: * the build tree (typically "build") * the release tree itself (only an issue if we ran "sdist" previously with --keep-temp, or it aborted) * any RCS or CVS directories """ config = self.get_finalized_command('config') filelist.exclude_pattern(config.cache_filename, anchor=1) build = self.get_finalized_command('build') filelist.exclude_pattern(None, prefix=build.build_base) filelist.exclude_pattern(None, prefix=self.dist_dir) # Trim the source code tree base_dir = self.distribution.get_fullname() filelist.exclude_pattern(None, prefix=base_dir) # Trim the documentation tree base_dir = '%s-docs-%s' % (self.distribution.get_name(), self.distribution.get_version()) filelist.exclude_pattern(None, prefix=base_dir) return filelist def make_distribution(self): # Ensure that all files in the source tree are included in the # would be distribution if not self.check_distribution(): return # Create the source code distribution sdist.sdist.make_distribution(self) # Create the documentation distribution base_dir = '%s-docs-%s' % (self.distribution.get_name(), self.distribution.get_version()) base_name = os.path.join(self.dist_dir, base_dir) # Make the documentation tree install = self.reinitialize_command('install') install.install_docs = base_dir install.ensure_finalized() for command_name in ('install_text', 'install_html'): command = self.reinitialize_command(command_name) command.ensure_finalized() command.run() # Add a PKG-INFO entry to allow for uploading to PyPI. self.distribution.metadata.write_pkg_info(base_dir) # Archive the documentation tree for fmt in self.formats: file = self.make_archive(base_name, fmt, base_dir=base_dir) self.archive_files.append(file) if hasattr(self.distribution, 'dist_files'): # Save it for the "upload" command as well. self.distribution.dist_files.append(('sdist', '', file)) if not self.keep_temp: dir_util.remove_tree(base_dir, dry_run=self.dry_run) return def check_distribution(self): # Start out with all files included allfiles = filelist.FileList() allfiles.set_allfiles([]) allfiles.extend(self.distribution.get_allfiles()) # Prune the same files as the distribution filelist self.prune_file_list(allfiles) # Exclude files included by other sub-packages validate_distributions = [self.distribution] if self.distribution.main_distribution: main_distribution = self.distribution.main_distribution validate_distributions.append(main_distribution) for name in main_distribution.package_options: dist = main_distribution.get_package_distribution(name) if dist is not self.distribution: # Remove the other sub-package source files for filename in dist.get_source_files(): filename = util.convert_path(filename) allfiles.exclude_pattern(filename) validate_distributions.append(dist) # Process the ignorable files for all available distributions for distribution in validate_distributions: for line in distribution.validate_templates: try: allfiles.process_template_line(line) except DistutilsTemplateError, msg: self.warn('in %s: %s' % (dist.package_file, msg)) # File list now complete -- sort it so that higher-level files # come first allfiles.sort() # Remove duplicates from the file list allfiles.remove_duplicates() # Ensure file paths are formatted the same # This removes any dot-slashes and converts all slashes to # OS-specific separators. dist_files = map(os.path.normpath, self.filelist.files) src_files = map(os.path.normpath, allfiles.files) valid = 1 for file in src_files: if file not in dist_files: self.warn('missing from source distribution: %s' % file) valid = 0 if not valid: self.warn('Not all source files in distribution') prompt = raw_input('Do you want to continue? (yes/no)') valid = prompt.lower() in ['y', 'yes'] return valid