|
1 | 1 | import contextlib
|
| 2 | +import io |
2 | 3 | import os
|
3 | 4 | import subprocess
|
4 | 5 | import sys
|
| 6 | +import tarfile |
| 7 | +import time |
5 | 8 | from pathlib import Path
|
6 | 9 |
|
7 | 10 | import path
|
8 | 11 | import pytest
|
9 | 12 |
|
| 13 | +from setuptools._normalization import safer_name |
| 14 | + |
10 | 15 | from . import contexts, environment
|
| 16 | +from .textwrap import DALS |
11 | 17 |
|
12 | 18 |
|
13 | 19 | @pytest.fixture
|
@@ -155,3 +161,183 @@ def bare_venv(tmp_path):
|
155 | 161 | env.create_opts = ['--no-setuptools', '--no-pip', '--no-wheel', '--no-seed']
|
156 | 162 | env.ensure_env()
|
157 | 163 | return env
|
| 164 | + |
| 165 | + |
| 166 | +def make_sdist(dist_path, files): |
| 167 | + """ |
| 168 | + Create a simple sdist tarball at dist_path, containing the files |
| 169 | + listed in ``files`` as ``(filename, content)`` tuples. |
| 170 | + """ |
| 171 | + |
| 172 | + # Distributions with only one file don't play well with pip. |
| 173 | + assert len(files) > 1 |
| 174 | + with tarfile.open(dist_path, 'w:gz') as dist: |
| 175 | + for filename, content in files: |
| 176 | + file_bytes = io.BytesIO(content.encode('utf-8')) |
| 177 | + file_info = tarfile.TarInfo(name=filename) |
| 178 | + file_info.size = len(file_bytes.getvalue()) |
| 179 | + file_info.mtime = int(time.time()) |
| 180 | + dist.addfile(file_info, fileobj=file_bytes) |
| 181 | + |
| 182 | + |
| 183 | +def make_trivial_sdist(dist_path, distname, version): |
| 184 | + """ |
| 185 | + Create a simple sdist tarball at dist_path, containing just a simple |
| 186 | + setup.py. |
| 187 | + """ |
| 188 | + |
| 189 | + make_sdist( |
| 190 | + dist_path, |
| 191 | + [ |
| 192 | + ( |
| 193 | + 'setup.py', |
| 194 | + DALS( |
| 195 | + f"""\ |
| 196 | + import setuptools |
| 197 | + setuptools.setup( |
| 198 | + name={distname!r}, |
| 199 | + version={version!r} |
| 200 | + ) |
| 201 | + """ |
| 202 | + ), |
| 203 | + ), |
| 204 | + ('setup.cfg', ''), |
| 205 | + ], |
| 206 | + ) |
| 207 | + |
| 208 | + |
| 209 | +def make_nspkg_sdist(dist_path, distname, version): |
| 210 | + """ |
| 211 | + Make an sdist tarball with distname and version which also contains one |
| 212 | + package with the same name as distname. The top-level package is |
| 213 | + designated a namespace package). |
| 214 | + """ |
| 215 | + # Assert that the distname contains at least one period |
| 216 | + assert '.' in distname |
| 217 | + |
| 218 | + parts = distname.split('.') |
| 219 | + nspackage = parts[0] |
| 220 | + |
| 221 | + packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)] |
| 222 | + |
| 223 | + setup_py = DALS( |
| 224 | + f"""\ |
| 225 | + import setuptools |
| 226 | + setuptools.setup( |
| 227 | + name={distname!r}, |
| 228 | + version={version!r}, |
| 229 | + packages={packages!r}, |
| 230 | + namespace_packages=[{nspackage!r}] |
| 231 | + ) |
| 232 | + """ |
| 233 | + ) |
| 234 | + |
| 235 | + init = "__import__('pkg_resources').declare_namespace(__name__)" |
| 236 | + |
| 237 | + files = [('setup.py', setup_py), (os.path.join(nspackage, '__init__.py'), init)] |
| 238 | + for package in packages[1:]: |
| 239 | + filename = os.path.join(*(package.split('.') + ['__init__.py'])) |
| 240 | + files.append((filename, '')) |
| 241 | + |
| 242 | + make_sdist(dist_path, files) |
| 243 | + |
| 244 | + |
| 245 | +def make_python_requires_sdist(dist_path, distname, version, python_requires): |
| 246 | + make_sdist( |
| 247 | + dist_path, |
| 248 | + [ |
| 249 | + ( |
| 250 | + 'setup.py', |
| 251 | + DALS( |
| 252 | + """\ |
| 253 | + import setuptools |
| 254 | + setuptools.setup( |
| 255 | + name={name!r}, |
| 256 | + version={version!r}, |
| 257 | + python_requires={python_requires!r}, |
| 258 | + ) |
| 259 | + """ |
| 260 | + ).format( |
| 261 | + name=distname, version=version, python_requires=python_requires |
| 262 | + ), |
| 263 | + ), |
| 264 | + ('setup.cfg', ''), |
| 265 | + ], |
| 266 | + ) |
| 267 | + |
| 268 | + |
| 269 | +def create_setup_requires_package( |
| 270 | + path, |
| 271 | + distname='foobar', |
| 272 | + version='0.1', |
| 273 | + make_package=make_trivial_sdist, |
| 274 | + setup_py_template=None, |
| 275 | + setup_attrs=None, |
| 276 | + use_setup_cfg=(), |
| 277 | +): |
| 278 | + """Creates a source tree under path for a trivial test package that has a |
| 279 | + single requirement in setup_requires--a tarball for that requirement is |
| 280 | + also created and added to the dependency_links argument. |
| 281 | +
|
| 282 | + ``distname`` and ``version`` refer to the name/version of the package that |
| 283 | + the test package requires via ``setup_requires``. The name of the test |
| 284 | + package itself is just 'test_pkg'. |
| 285 | + """ |
| 286 | + |
| 287 | + normalized_distname = safer_name(distname) |
| 288 | + test_setup_attrs = { |
| 289 | + 'name': 'test_pkg', |
| 290 | + 'version': '0.0', |
| 291 | + 'setup_requires': [f'{normalized_distname}=={version}'], |
| 292 | + 'dependency_links': [os.path.abspath(path)], |
| 293 | + } |
| 294 | + if setup_attrs: |
| 295 | + test_setup_attrs.update(setup_attrs) |
| 296 | + |
| 297 | + test_pkg = os.path.join(path, 'test_pkg') |
| 298 | + os.mkdir(test_pkg) |
| 299 | + |
| 300 | + # setup.cfg |
| 301 | + if use_setup_cfg: |
| 302 | + options = [] |
| 303 | + metadata = [] |
| 304 | + for name in use_setup_cfg: |
| 305 | + value = test_setup_attrs.pop(name) |
| 306 | + if name in 'name version'.split(): |
| 307 | + section = metadata |
| 308 | + else: |
| 309 | + section = options |
| 310 | + if isinstance(value, (tuple, list)): |
| 311 | + value = ';'.join(value) |
| 312 | + section.append(f'{name}: {value}') |
| 313 | + test_setup_cfg_contents = DALS( |
| 314 | + """ |
| 315 | + [metadata] |
| 316 | + {metadata} |
| 317 | + [options] |
| 318 | + {options} |
| 319 | + """ |
| 320 | + ).format( |
| 321 | + options='\n'.join(options), |
| 322 | + metadata='\n'.join(metadata), |
| 323 | + ) |
| 324 | + else: |
| 325 | + test_setup_cfg_contents = '' |
| 326 | + with open(os.path.join(test_pkg, 'setup.cfg'), 'w', encoding="utf-8") as f: |
| 327 | + f.write(test_setup_cfg_contents) |
| 328 | + |
| 329 | + # setup.py |
| 330 | + if setup_py_template is None: |
| 331 | + setup_py_template = DALS( |
| 332 | + """\ |
| 333 | + import setuptools |
| 334 | + setuptools.setup(**%r) |
| 335 | + """ |
| 336 | + ) |
| 337 | + with open(os.path.join(test_pkg, 'setup.py'), 'w', encoding="utf-8") as f: |
| 338 | + f.write(setup_py_template % test_setup_attrs) |
| 339 | + |
| 340 | + foobar_path = os.path.join(path, f'{normalized_distname}-{version}.tar.gz') |
| 341 | + make_package(foobar_path, distname, version) |
| 342 | + |
| 343 | + return test_pkg |
0 commit comments