codekingpro/portable-devtools
114k
1from distutils import log2import distutils.command.sdist as orig3import os4import contextlib5from itertools import chain6 7from .._importlib import metadata8from .build import _ORIGINAL_SUBCOMMANDS9 10_default_revctrl = list11 12 13def walk_revctrl(dirname=''):14 """Find all files under revision control"""15 for ep in metadata.entry_points(group='setuptools.file_finders'):16 yield from ep.load()(dirname)17 18 19class sdist(orig.sdist):20 """Smart sdist that finds anything supported by revision control"""21 22 user_options = [23 ('formats=', None, "formats for source distribution (comma-separated list)"),24 (25 'keep-temp',26 'k',27 "keep the distribution tree around after creating " + "archive file(s)",28 ),29 (30 'dist-dir=',31 'd',32 "directory to put the source distribution archive(s) in " "[default: dist]",33 ),34 (35 'owner=',36 'u',37 "Owner name used when creating a tar file [default: current user]",38 ),39 (40 'group=',41 'g',42 "Group name used when creating a tar file [default: current group]",43 ),44 ]45 46 negative_opt = {}47 48 README_EXTENSIONS = ['', '.rst', '.txt', '.md']49 READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS)50 51 def run(self):52 self.run_command('egg_info')53 ei_cmd = self.get_finalized_command('egg_info')54 self.filelist = ei_cmd.filelist55 self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt'))56 self.check_readme()57 58 # Run sub commands59 for cmd_name in self.get_sub_commands():60 self.run_command(cmd_name)61 62 self.make_distribution()63 64 dist_files = getattr(self.distribution, 'dist_files', [])65 for file in self.archive_files:66 data = ('sdist', '', file)67 if data not in dist_files:68 dist_files.append(data)69 70 def initialize_options(self):71 orig.sdist.initialize_options(self)72 73 def make_distribution(self):74 """75 Workaround for #51676 """77 with self._remove_os_link():78 orig.sdist.make_distribution(self)79 80 @staticmethod81 @contextlib.contextmanager82 def _remove_os_link():83 """84 In a context, remove and restore os.link if it exists85 """86 87 class NoValue:88 pass89 90 orig_val = getattr(os, 'link', NoValue)91 try:92 del os.link93 except Exception:94 pass95 try:96 yield97 finally:98 if orig_val is not NoValue:99 os.link = orig_val100 101 def add_defaults(self):102 super().add_defaults()103 self._add_defaults_build_sub_commands()104 105 def _add_defaults_optional(self):106 super()._add_defaults_optional()107 if os.path.isfile('pyproject.toml'):108 self.filelist.append('pyproject.toml')109 110 def _add_defaults_python(self):111 """getting python files"""112 if self.distribution.has_pure_modules():113 build_py = self.get_finalized_command('build_py')114 self.filelist.extend(build_py.get_source_files())115 self._add_data_files(self._safe_data_files(build_py))116 117 def _add_defaults_build_sub_commands(self):118 build = self.get_finalized_command("build")119 missing_cmds = set(build.get_sub_commands()) - _ORIGINAL_SUBCOMMANDS120 # ^-- the original built-in sub-commands are already handled by default.121 cmds = (self.get_finalized_command(c) for c in missing_cmds)122 files = (c.get_source_files() for c in cmds if hasattr(c, "get_source_files"))123 self.filelist.extend(chain.from_iterable(files))124 125 def _safe_data_files(self, build_py):126 """127 Since the ``sdist`` class is also used to compute the MANIFEST128 (via :obj:`setuptools.command.egg_info.manifest_maker`),129 there might be recursion problems when trying to obtain the list of130 data_files and ``include_package_data=True`` (which in turn depends on131 the files included in the MANIFEST).132 133 To avoid that, ``manifest_maker`` should be able to overwrite this134 method and avoid recursive attempts to build/analyze the MANIFEST.135 """136 return build_py.data_files137 138 def _add_data_files(self, data_files):139 """140 Add data files as found in build_py.data_files.141 """142 self.filelist.extend(143 os.path.join(src_dir, name)144 for _, src_dir, _, filenames in data_files145 for name in filenames146 )147 148 def _add_defaults_data_files(self):149 try:150 super()._add_defaults_data_files()151 except TypeError:152 log.warn("data_files contains unexpected objects")153 154 def check_readme(self):155 for f in self.READMES:156 if os.path.exists(f):157 return158 else:159 self.warn(160 "standard file not found: should have one of " + ', '.join(self.READMES)161 )162 163 def make_release_tree(self, base_dir, files):164 orig.sdist.make_release_tree(self, base_dir, files)165 166 # Save any egg_info command line options used to create this sdist167 dest = os.path.join(base_dir, 'setup.cfg')168 if hasattr(os, 'link') and os.path.exists(dest):169 # unlink and re-copy, since it might be hard-linked, and170 # we don't want to change the source version171 os.unlink(dest)172 self.copy_file('setup.cfg', dest)173 174 self.get_finalized_command('egg_info').save_version_info(dest)175 176 def _manifest_is_not_generated(self):177 # check for special comment used in 2.7.1 and higher178 if not os.path.isfile(self.manifest):179 return False180 181 with open(self.manifest, 'rb') as fp:182 first_line = fp.readline()183 return first_line != b'# file GENERATED by distutils, do NOT edit\n'184 185 def read_manifest(self):186 """Read the manifest file (named by 'self.manifest') and use it to187 fill in 'self.filelist', the list of files to include in the source188 distribution.189 """190 log.info("reading manifest file '%s'", self.manifest)191 manifest = open(self.manifest, 'rb')192 for line in manifest:193 # The manifest must contain UTF-8. See #303.194 try:195 line = line.decode('UTF-8')196 except UnicodeDecodeError:197 log.warn("%r not UTF-8 decodable -- skipping" % line)198 continue199 # ignore comments and blank lines200 line = line.strip()201 if line.startswith('#') or not line:202 continue203 self.filelist.append(line)204 manifest.close()205 