1from distutils import log 2import distutils.command.sdist as orig 3import os 4import sys 5import io 6import contextlib 7 8from setuptools.extern import six 9 10from .py36compat import sdist_add_defaults 11 12import pkg_resources 13 14_default_revctrl = list 15 16 17def walk_revctrl(dirname=''): 18 """Find all files under revision control""" 19 for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): 20 for item in ep.load()(dirname): 21 yield item 22 23 24class sdist(sdist_add_defaults, orig.sdist): 25 """Smart sdist that finds anything supported by revision control""" 26 27 user_options = [ 28 ('formats=', None, 29 "formats for source distribution (comma-separated list)"), 30 ('keep-temp', 'k', 31 "keep the distribution tree around after creating " + 32 "archive file(s)"), 33 ('dist-dir=', 'd', 34 "directory to put the source distribution archive(s) in " 35 "[default: dist]"), 36 ] 37 38 negative_opt = {} 39 40 README_EXTENSIONS = ['', '.rst', '.txt', '.md'] 41 READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) 42 43 def run(self): 44 self.run_command('egg_info') 45 ei_cmd = self.get_finalized_command('egg_info') 46 self.filelist = ei_cmd.filelist 47 self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt')) 48 self.check_readme() 49 50 # Run sub commands 51 for cmd_name in self.get_sub_commands(): 52 self.run_command(cmd_name) 53 54 self.make_distribution() 55 56 dist_files = getattr(self.distribution, 'dist_files', []) 57 for file in self.archive_files: 58 data = ('sdist', '', file) 59 if data not in dist_files: 60 dist_files.append(data) 61 62 def initialize_options(self): 63 orig.sdist.initialize_options(self) 64 65 self._default_to_gztar() 66 67 def _default_to_gztar(self): 68 # only needed on Python prior to 3.6. 69 if sys.version_info >= (3, 6, 0, 'beta', 1): 70 return 71 self.formats = ['gztar'] 72 73 def make_distribution(self): 74 """ 75 Workaround for #516 76 """ 77 with self._remove_os_link(): 78 orig.sdist.make_distribution(self) 79 80 @staticmethod 81 @contextlib.contextmanager 82 def _remove_os_link(): 83 """ 84 In a context, remove and restore os.link if it exists 85 """ 86 87 class NoValue: 88 pass 89 90 orig_val = getattr(os, 'link', NoValue) 91 try: 92 del os.link 93 except Exception: 94 pass 95 try: 96 yield 97 finally: 98 if orig_val is not NoValue: 99 setattr(os, 'link', orig_val) 100 101 def __read_template_hack(self): 102 # This grody hack closes the template file (MANIFEST.in) if an 103 # exception occurs during read_template. 104 # Doing so prevents an error when easy_install attempts to delete the 105 # file. 106 try: 107 orig.sdist.read_template(self) 108 except Exception: 109 _, _, tb = sys.exc_info() 110 tb.tb_next.tb_frame.f_locals['template'].close() 111 raise 112 113 # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle 114 # has been fixed, so only override the method if we're using an earlier 115 # Python. 116 has_leaky_handle = ( 117 sys.version_info < (2, 7, 2) 118 or (3, 0) <= sys.version_info < (3, 1, 4) 119 or (3, 2) <= sys.version_info < (3, 2, 1) 120 ) 121 if has_leaky_handle: 122 read_template = __read_template_hack 123 124 def _add_defaults_python(self): 125 """getting python files""" 126 if self.distribution.has_pure_modules(): 127 build_py = self.get_finalized_command('build_py') 128 self.filelist.extend(build_py.get_source_files()) 129 # This functionality is incompatible with include_package_data, and 130 # will in fact create an infinite recursion if include_package_data 131 # is True. Use of include_package_data will imply that 132 # distutils-style automatic handling of package_data is disabled 133 if not self.distribution.include_package_data: 134 for _, src_dir, _, filenames in build_py.data_files: 135 self.filelist.extend([os.path.join(src_dir, filename) 136 for filename in filenames]) 137 138 def _add_defaults_data_files(self): 139 try: 140 if six.PY2: 141 sdist_add_defaults._add_defaults_data_files(self) 142 else: 143 super()._add_defaults_data_files() 144 except TypeError: 145 log.warn("data_files contains unexpected objects") 146 147 def check_readme(self): 148 for f in self.READMES: 149 if os.path.exists(f): 150 return 151 else: 152 self.warn( 153 "standard file not found: should have one of " + 154 ', '.join(self.READMES) 155 ) 156 157 def make_release_tree(self, base_dir, files): 158 orig.sdist.make_release_tree(self, base_dir, files) 159 160 # Save any egg_info command line options used to create this sdist 161 dest = os.path.join(base_dir, 'setup.cfg') 162 if hasattr(os, 'link') and os.path.exists(dest): 163 # unlink and re-copy, since it might be hard-linked, and 164 # we don't want to change the source version 165 os.unlink(dest) 166 self.copy_file('setup.cfg', dest) 167 168 self.get_finalized_command('egg_info').save_version_info(dest) 169 170 def _manifest_is_not_generated(self): 171 # check for special comment used in 2.7.1 and higher 172 if not os.path.isfile(self.manifest): 173 return False 174 175 with io.open(self.manifest, 'rb') as fp: 176 first_line = fp.readline() 177 return (first_line != 178 '# file GENERATED by distutils, do NOT edit\n'.encode()) 179 180 def read_manifest(self): 181 """Read the manifest file (named by 'self.manifest') and use it to 182 fill in 'self.filelist', the list of files to include in the source 183 distribution. 184 """ 185 log.info("reading manifest file '%s'", self.manifest) 186 manifest = open(self.manifest, 'rb') 187 for line in manifest: 188 # The manifest must contain UTF-8. See #303. 189 if six.PY3: 190 try: 191 line = line.decode('UTF-8') 192 except UnicodeDecodeError: 193 log.warn("%r not UTF-8 decodable -- skipping" % line) 194 continue 195 # ignore comments and blank lines 196 line = line.strip() 197 if line.startswith('#') or not line: 198 continue 199 self.filelist.append(line) 200 manifest.close() 201