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