test_bdist_wheel.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. from __future__ import annotations
  2. import builtins
  3. import importlib
  4. import os.path
  5. import platform
  6. import shutil
  7. import stat
  8. import struct
  9. import sys
  10. import sysconfig
  11. from contextlib import suppress
  12. from inspect import cleandoc
  13. from zipfile import ZipFile
  14. import jaraco.path
  15. import pytest
  16. from packaging import tags
  17. import setuptools
  18. from setuptools.command.bdist_wheel import bdist_wheel, get_abi_tag
  19. from setuptools.dist import Distribution
  20. from setuptools.warnings import SetuptoolsDeprecationWarning
  21. from distutils.core import run_setup
  22. DEFAULT_FILES = {
  23. "dummy_dist-1.0.dist-info/top_level.txt",
  24. "dummy_dist-1.0.dist-info/METADATA",
  25. "dummy_dist-1.0.dist-info/WHEEL",
  26. "dummy_dist-1.0.dist-info/RECORD",
  27. }
  28. DEFAULT_LICENSE_FILES = {
  29. "LICENSE",
  30. "LICENSE.txt",
  31. "LICENCE",
  32. "LICENCE.txt",
  33. "COPYING",
  34. "COPYING.md",
  35. "NOTICE",
  36. "NOTICE.rst",
  37. "AUTHORS",
  38. "AUTHORS.txt",
  39. }
  40. OTHER_IGNORED_FILES = {
  41. "LICENSE~",
  42. "AUTHORS~",
  43. }
  44. SETUPPY_EXAMPLE = """\
  45. from setuptools import setup
  46. setup(
  47. name='dummy_dist',
  48. version='1.0',
  49. )
  50. """
  51. EXAMPLES = {
  52. "dummy-dist": {
  53. "setup.py": SETUPPY_EXAMPLE,
  54. "licenses_dir": {"DUMMYFILE": ""},
  55. **dict.fromkeys(DEFAULT_LICENSE_FILES | OTHER_IGNORED_FILES, ""),
  56. },
  57. "simple-dist": {
  58. "setup.py": cleandoc(
  59. """
  60. from setuptools import setup
  61. setup(
  62. name="simple.dist",
  63. version="0.1",
  64. description="A testing distribution \N{SNOWMAN}",
  65. extras_require={"voting": ["beaglevote"]},
  66. )
  67. """
  68. ),
  69. "simpledist": "",
  70. },
  71. "complex-dist": {
  72. "setup.py": cleandoc(
  73. """
  74. from setuptools import setup
  75. setup(
  76. name="complex-dist",
  77. version="0.1",
  78. description="Another testing distribution \N{SNOWMAN}",
  79. long_description="Another testing distribution \N{SNOWMAN}",
  80. author="Illustrious Author",
  81. author_email="illustrious@example.org",
  82. url="http://example.org/exemplary",
  83. packages=["complexdist"],
  84. setup_requires=["setuptools"],
  85. install_requires=["quux", "splort"],
  86. extras_require={"simple": ["simple.dist"]},
  87. entry_points={
  88. "console_scripts": [
  89. "complex-dist=complexdist:main",
  90. "complex-dist2=complexdist:main",
  91. ],
  92. },
  93. )
  94. """
  95. ),
  96. "complexdist": {"__init__.py": "def main(): return"},
  97. },
  98. "headers-dist": {
  99. "setup.py": cleandoc(
  100. """
  101. from setuptools import setup
  102. setup(
  103. name="headers.dist",
  104. version="0.1",
  105. description="A distribution with headers",
  106. headers=["header.h"],
  107. )
  108. """
  109. ),
  110. "headersdist.py": "",
  111. "header.h": "",
  112. },
  113. "commasinfilenames-dist": {
  114. "setup.py": cleandoc(
  115. """
  116. from setuptools import setup
  117. setup(
  118. name="testrepo",
  119. version="0.1",
  120. packages=["mypackage"],
  121. description="A test package with commas in file names",
  122. include_package_data=True,
  123. package_data={"mypackage.data": ["*"]},
  124. )
  125. """
  126. ),
  127. "mypackage": {
  128. "__init__.py": "",
  129. "data": {"__init__.py": "", "1,2,3.txt": ""},
  130. },
  131. "testrepo-0.1.0": {
  132. "mypackage": {"__init__.py": ""},
  133. },
  134. },
  135. "unicode-dist": {
  136. "setup.py": cleandoc(
  137. """
  138. from setuptools import setup
  139. setup(
  140. name="unicode.dist",
  141. version="0.1",
  142. description="A testing distribution \N{SNOWMAN}",
  143. packages=["unicodedist"],
  144. zip_safe=True,
  145. )
  146. """
  147. ),
  148. "unicodedist": {"__init__.py": "", "åäö_日本語.py": ""},
  149. },
  150. "utf8-metadata-dist": {
  151. "setup.cfg": cleandoc(
  152. """
  153. [metadata]
  154. name = utf8-metadata-dist
  155. version = 42
  156. author_email = "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
  157. long_description = file: README.rst
  158. """
  159. ),
  160. "README.rst": "UTF-8 描述 説明",
  161. },
  162. "licenses-dist": {
  163. "setup.cfg": cleandoc(
  164. """
  165. [metadata]
  166. name = licenses-dist
  167. version = 1.0
  168. license_files = **/LICENSE
  169. """
  170. ),
  171. "LICENSE": "",
  172. "src": {
  173. "vendor": {"LICENSE": ""},
  174. },
  175. },
  176. }
  177. if sys.platform != "win32":
  178. # ABI3 extensions don't really work on Windows
  179. EXAMPLES["abi3extension-dist"] = {
  180. "setup.py": cleandoc(
  181. """
  182. from setuptools import Extension, setup
  183. setup(
  184. name="extension.dist",
  185. version="0.1",
  186. description="A testing distribution \N{SNOWMAN}",
  187. ext_modules=[
  188. Extension(
  189. name="extension", sources=["extension.c"], py_limited_api=True
  190. )
  191. ],
  192. )
  193. """
  194. ),
  195. "setup.cfg": "[bdist_wheel]\npy_limited_api=cp32",
  196. "extension.c": "#define Py_LIMITED_API 0x03020000\n#include <Python.h>",
  197. }
  198. def bdist_wheel_cmd(**kwargs):
  199. """Run command in the same process so that it is easier to collect coverage"""
  200. dist_obj = (
  201. run_setup("setup.py", stop_after="init")
  202. if os.path.exists("setup.py")
  203. else Distribution({"script_name": "%%build_meta%%"})
  204. )
  205. dist_obj.parse_config_files()
  206. cmd = bdist_wheel(dist_obj)
  207. for attr, value in kwargs.items():
  208. setattr(cmd, attr, value)
  209. cmd.finalize_options()
  210. return cmd
  211. def mkexample(tmp_path_factory, name):
  212. basedir = tmp_path_factory.mktemp(name)
  213. jaraco.path.build(EXAMPLES[name], prefix=str(basedir))
  214. return basedir
  215. @pytest.fixture(scope="session")
  216. def wheel_paths(tmp_path_factory):
  217. build_base = tmp_path_factory.mktemp("build")
  218. dist_dir = tmp_path_factory.mktemp("dist")
  219. for name in EXAMPLES:
  220. example_dir = mkexample(tmp_path_factory, name)
  221. build_dir = build_base / name
  222. with jaraco.path.DirectoryStack().context(example_dir):
  223. bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
  224. return sorted(str(fname) for fname in dist_dir.glob("*.whl"))
  225. @pytest.fixture
  226. def dummy_dist(tmp_path_factory):
  227. return mkexample(tmp_path_factory, "dummy-dist")
  228. @pytest.fixture
  229. def licenses_dist(tmp_path_factory):
  230. return mkexample(tmp_path_factory, "licenses-dist")
  231. def test_no_scripts(wheel_paths):
  232. """Make sure entry point scripts are not generated."""
  233. path = next(path for path in wheel_paths if "complex_dist" in path)
  234. for entry in ZipFile(path).infolist():
  235. assert ".data/scripts/" not in entry.filename
  236. def test_unicode_record(wheel_paths):
  237. path = next(path for path in wheel_paths if "unicode_dist" in path)
  238. with ZipFile(path) as zf:
  239. record = zf.read("unicode_dist-0.1.dist-info/RECORD")
  240. assert "åäö_日本語.py".encode() in record
  241. UTF8_PKG_INFO = """\
  242. Metadata-Version: 2.1
  243. Name: helloworld
  244. Version: 42
  245. Author-email: "John X. Ãørçeč" <john@utf8.org>, Γαμα קּ 東 <gama@utf8.org>
  246. UTF-8 描述 説明
  247. """
  248. def test_preserve_unicode_metadata(monkeypatch, tmp_path):
  249. monkeypatch.chdir(tmp_path)
  250. egginfo = tmp_path / "dummy_dist.egg-info"
  251. distinfo = tmp_path / "dummy_dist.dist-info"
  252. egginfo.mkdir()
  253. (egginfo / "PKG-INFO").write_text(UTF8_PKG_INFO, encoding="utf-8")
  254. (egginfo / "dependency_links.txt").touch()
  255. class simpler_bdist_wheel(bdist_wheel):
  256. """Avoid messing with setuptools/distutils internals"""
  257. def __init__(self) -> None:
  258. pass
  259. @property
  260. def license_paths(self):
  261. return []
  262. cmd_obj = simpler_bdist_wheel()
  263. cmd_obj.egg2dist(egginfo, distinfo)
  264. metadata = (distinfo / "METADATA").read_text(encoding="utf-8")
  265. assert 'Author-email: "John X. Ãørçeč"' in metadata
  266. assert "Γαμα קּ 東 " in metadata
  267. assert "UTF-8 描述 説明" in metadata
  268. def test_licenses_default(dummy_dist, monkeypatch, tmp_path):
  269. monkeypatch.chdir(dummy_dist)
  270. bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
  271. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  272. license_files = {
  273. "dummy_dist-1.0.dist-info/licenses/" + fname
  274. for fname in DEFAULT_LICENSE_FILES
  275. }
  276. assert set(wf.namelist()) == DEFAULT_FILES | license_files
  277. def test_licenses_deprecated(dummy_dist, monkeypatch, tmp_path):
  278. dummy_dist.joinpath("setup.cfg").write_text(
  279. "[metadata]\nlicense_file=licenses_dir/DUMMYFILE", encoding="utf-8"
  280. )
  281. monkeypatch.chdir(dummy_dist)
  282. bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
  283. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  284. license_files = {"dummy_dist-1.0.dist-info/licenses/licenses_dir/DUMMYFILE"}
  285. assert set(wf.namelist()) == DEFAULT_FILES | license_files
  286. @pytest.mark.parametrize(
  287. ("config_file", "config"),
  288. [
  289. ("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*\n LICENSE"),
  290. ("setup.cfg", "[metadata]\nlicense_files=licenses_dir/*, LICENSE"),
  291. (
  292. "setup.py",
  293. SETUPPY_EXAMPLE.replace(
  294. ")", " license_files=['licenses_dir/DUMMYFILE', 'LICENSE'])"
  295. ),
  296. ),
  297. ],
  298. )
  299. def test_licenses_override(dummy_dist, monkeypatch, tmp_path, config_file, config):
  300. dummy_dist.joinpath(config_file).write_text(config, encoding="utf-8")
  301. monkeypatch.chdir(dummy_dist)
  302. bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
  303. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  304. license_files = {
  305. "dummy_dist-1.0.dist-info/licenses/" + fname
  306. for fname in {"licenses_dir/DUMMYFILE", "LICENSE"}
  307. }
  308. assert set(wf.namelist()) == DEFAULT_FILES | license_files
  309. metadata = wf.read("dummy_dist-1.0.dist-info/METADATA").decode("utf8")
  310. assert "License-File: licenses_dir/DUMMYFILE" in metadata
  311. assert "License-File: LICENSE" in metadata
  312. def test_licenses_preserve_folder_structure(licenses_dist, monkeypatch, tmp_path):
  313. monkeypatch.chdir(licenses_dist)
  314. bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
  315. print(os.listdir("dist"))
  316. with ZipFile("dist/licenses_dist-1.0-py3-none-any.whl") as wf:
  317. default_files = {name.replace("dummy_", "licenses_") for name in DEFAULT_FILES}
  318. license_files = {
  319. "licenses_dist-1.0.dist-info/licenses/LICENSE",
  320. "licenses_dist-1.0.dist-info/licenses/src/vendor/LICENSE",
  321. }
  322. assert set(wf.namelist()) == default_files | license_files
  323. metadata = wf.read("licenses_dist-1.0.dist-info/METADATA").decode("utf8")
  324. assert "License-File: src/vendor/LICENSE" in metadata
  325. assert "License-File: LICENSE" in metadata
  326. def test_licenses_disabled(dummy_dist, monkeypatch, tmp_path):
  327. dummy_dist.joinpath("setup.cfg").write_text(
  328. "[metadata]\nlicense_files=\n", encoding="utf-8"
  329. )
  330. monkeypatch.chdir(dummy_dist)
  331. bdist_wheel_cmd(bdist_dir=str(tmp_path)).run()
  332. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  333. assert set(wf.namelist()) == DEFAULT_FILES
  334. def test_build_number(dummy_dist, monkeypatch, tmp_path):
  335. monkeypatch.chdir(dummy_dist)
  336. bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2").run()
  337. with ZipFile("dist/dummy_dist-1.0-2-py3-none-any.whl") as wf:
  338. filenames = set(wf.namelist())
  339. assert "dummy_dist-1.0.dist-info/RECORD" in filenames
  340. assert "dummy_dist-1.0.dist-info/METADATA" in filenames
  341. def test_universal_deprecated(dummy_dist, monkeypatch, tmp_path):
  342. monkeypatch.chdir(dummy_dist)
  343. with pytest.warns(SetuptoolsDeprecationWarning, match=".*universal is deprecated"):
  344. bdist_wheel_cmd(bdist_dir=str(tmp_path), universal=True).run()
  345. # For now we still respect the option
  346. assert os.path.exists("dist/dummy_dist-1.0-py2.py3-none-any.whl")
  347. EXTENSION_EXAMPLE = """\
  348. #include <Python.h>
  349. static PyMethodDef methods[] = {
  350. { NULL, NULL, 0, NULL }
  351. };
  352. static struct PyModuleDef module_def = {
  353. PyModuleDef_HEAD_INIT,
  354. "extension",
  355. "Dummy extension module",
  356. -1,
  357. methods
  358. };
  359. PyMODINIT_FUNC PyInit_extension(void) {
  360. return PyModule_Create(&module_def);
  361. }
  362. """
  363. EXTENSION_SETUPPY = """\
  364. from __future__ import annotations
  365. from setuptools import Extension, setup
  366. setup(
  367. name="extension.dist",
  368. version="0.1",
  369. description="A testing distribution \N{SNOWMAN}",
  370. ext_modules=[Extension(name="extension", sources=["extension.c"])],
  371. )
  372. """
  373. @pytest.mark.filterwarnings(
  374. "once:Config variable '.*' is unset.*, Python ABI tag may be incorrect"
  375. )
  376. def test_limited_abi(monkeypatch, tmp_path, tmp_path_factory):
  377. """Test that building a binary wheel with the limited ABI works."""
  378. source_dir = tmp_path_factory.mktemp("extension_dist")
  379. (source_dir / "setup.py").write_text(EXTENSION_SETUPPY, encoding="utf-8")
  380. (source_dir / "extension.c").write_text(EXTENSION_EXAMPLE, encoding="utf-8")
  381. build_dir = tmp_path.joinpath("build")
  382. dist_dir = tmp_path.joinpath("dist")
  383. monkeypatch.chdir(source_dir)
  384. bdist_wheel_cmd(bdist_dir=str(build_dir), dist_dir=str(dist_dir)).run()
  385. def test_build_from_readonly_tree(dummy_dist, monkeypatch, tmp_path):
  386. basedir = str(tmp_path.joinpath("dummy"))
  387. shutil.copytree(str(dummy_dist), basedir)
  388. monkeypatch.chdir(basedir)
  389. # Make the tree read-only
  390. for root, _dirs, files in os.walk(basedir):
  391. for fname in files:
  392. os.chmod(os.path.join(root, fname), stat.S_IREAD)
  393. bdist_wheel_cmd().run()
  394. @pytest.mark.parametrize(
  395. ("option", "compress_type"),
  396. list(bdist_wheel.supported_compressions.items()),
  397. ids=list(bdist_wheel.supported_compressions),
  398. )
  399. def test_compression(dummy_dist, monkeypatch, tmp_path, option, compress_type):
  400. monkeypatch.chdir(dummy_dist)
  401. bdist_wheel_cmd(bdist_dir=str(tmp_path), compression=option).run()
  402. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  403. filenames = set(wf.namelist())
  404. assert "dummy_dist-1.0.dist-info/RECORD" in filenames
  405. assert "dummy_dist-1.0.dist-info/METADATA" in filenames
  406. for zinfo in wf.filelist:
  407. assert zinfo.compress_type == compress_type
  408. def test_wheelfile_line_endings(wheel_paths):
  409. for path in wheel_paths:
  410. with ZipFile(path) as wf:
  411. wheelfile = next(fn for fn in wf.filelist if fn.filename.endswith("WHEEL"))
  412. wheelfile_contents = wf.read(wheelfile)
  413. assert b"\r" not in wheelfile_contents
  414. def test_unix_epoch_timestamps(dummy_dist, monkeypatch, tmp_path):
  415. monkeypatch.setenv("SOURCE_DATE_EPOCH", "0")
  416. monkeypatch.chdir(dummy_dist)
  417. bdist_wheel_cmd(bdist_dir=str(tmp_path), build_number="2a").run()
  418. with ZipFile("dist/dummy_dist-1.0-2a-py3-none-any.whl") as wf:
  419. for zinfo in wf.filelist:
  420. assert zinfo.date_time >= (1980, 1, 1, 0, 0, 0) # min epoch is used
  421. def test_get_abi_tag_windows(monkeypatch):
  422. monkeypatch.setattr(tags, "interpreter_name", lambda: "cp")
  423. monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313-win_amd64")
  424. assert get_abi_tag() == "cp313"
  425. monkeypatch.setattr(sys, "gettotalrefcount", lambda: 1, False)
  426. assert get_abi_tag() == "cp313d"
  427. monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "cp313t-win_amd64")
  428. assert get_abi_tag() == "cp313td"
  429. monkeypatch.delattr(sys, "gettotalrefcount")
  430. assert get_abi_tag() == "cp313t"
  431. def test_get_abi_tag_pypy_old(monkeypatch):
  432. monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
  433. monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy36-pp73")
  434. assert get_abi_tag() == "pypy36_pp73"
  435. def test_get_abi_tag_pypy_new(monkeypatch):
  436. monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "pypy37-pp73-darwin")
  437. monkeypatch.setattr(tags, "interpreter_name", lambda: "pp")
  438. assert get_abi_tag() == "pypy37_pp73"
  439. def test_get_abi_tag_graalpy(monkeypatch):
  440. monkeypatch.setattr(
  441. sysconfig, "get_config_var", lambda x: "graalpy231-310-native-x86_64-linux"
  442. )
  443. monkeypatch.setattr(tags, "interpreter_name", lambda: "graalpy")
  444. assert get_abi_tag() == "graalpy231_310_native"
  445. def test_get_abi_tag_fallback(monkeypatch):
  446. monkeypatch.setattr(sysconfig, "get_config_var", lambda x: "unknown-python-310")
  447. monkeypatch.setattr(tags, "interpreter_name", lambda: "unknown-python")
  448. assert get_abi_tag() == "unknown_python_310"
  449. def test_platform_with_space(dummy_dist, monkeypatch):
  450. """Ensure building on platforms with a space in the name succeed."""
  451. monkeypatch.chdir(dummy_dist)
  452. bdist_wheel_cmd(plat_name="isilon onefs").run()
  453. def test_data_dir_with_tag_build(monkeypatch, tmp_path):
  454. """
  455. Setuptools allow authors to set PEP 440's local version segments
  456. using ``egg_info.tag_build``. This should be reflected not only in the
  457. ``.whl`` file name, but also in the ``.dist-info`` and ``.data`` dirs.
  458. See pypa/setuptools#3997.
  459. """
  460. monkeypatch.chdir(tmp_path)
  461. files = {
  462. "setup.py": """
  463. from setuptools import setup
  464. setup(headers=["hello.h"])
  465. """,
  466. "setup.cfg": """
  467. [metadata]
  468. name = test
  469. version = 1.0
  470. [options.data_files]
  471. hello/world = file.txt
  472. [egg_info]
  473. tag_build = +what
  474. tag_date = 0
  475. """,
  476. "file.txt": "",
  477. "hello.h": "",
  478. }
  479. for file, content in files.items():
  480. with open(file, "w", encoding="utf-8") as fh:
  481. fh.write(cleandoc(content))
  482. bdist_wheel_cmd().run()
  483. # Ensure .whl, .dist-info and .data contain the local segment
  484. wheel_path = "dist/test-1.0+what-py3-none-any.whl"
  485. assert os.path.exists(wheel_path)
  486. entries = set(ZipFile(wheel_path).namelist())
  487. for expected in (
  488. "test-1.0+what.data/headers/hello.h",
  489. "test-1.0+what.data/data/hello/world/file.txt",
  490. "test-1.0+what.dist-info/METADATA",
  491. "test-1.0+what.dist-info/WHEEL",
  492. ):
  493. assert expected in entries
  494. for not_expected in (
  495. "test.data/headers/hello.h",
  496. "test-1.0.data/data/hello/world/file.txt",
  497. "test.dist-info/METADATA",
  498. "test-1.0.dist-info/WHEEL",
  499. ):
  500. assert not_expected not in entries
  501. @pytest.mark.parametrize(
  502. ("reported", "expected"),
  503. [("linux-x86_64", "linux_i686"), ("linux-aarch64", "linux_armv7l")],
  504. )
  505. @pytest.mark.skipif(
  506. platform.system() != "Linux", reason="Only makes sense to test on Linux"
  507. )
  508. def test_platform_linux32(reported, expected, monkeypatch):
  509. monkeypatch.setattr(struct, "calcsize", lambda x: 4)
  510. dist = setuptools.Distribution()
  511. cmd = bdist_wheel(dist)
  512. cmd.plat_name = reported
  513. cmd.root_is_pure = False
  514. _, _, actual = cmd.get_tag()
  515. assert actual == expected
  516. def test_no_ctypes(monkeypatch) -> None:
  517. def _fake_import(name: str, *args, **kwargs):
  518. if name == "ctypes":
  519. raise ModuleNotFoundError(f"No module named {name}")
  520. return importlib.__import__(name, *args, **kwargs)
  521. with suppress(KeyError):
  522. monkeypatch.delitem(sys.modules, "wheel.macosx_libfile")
  523. # Install an importer shim that refuses to load ctypes
  524. monkeypatch.setattr(builtins, "__import__", _fake_import)
  525. with pytest.raises(ModuleNotFoundError, match="No module named ctypes"):
  526. import wheel.macosx_libfile # noqa: F401
  527. # Unload and reimport the bdist_wheel command module to make sure it won't try to
  528. # import ctypes
  529. monkeypatch.delitem(sys.modules, "setuptools.command.bdist_wheel")
  530. import setuptools.command.bdist_wheel # noqa: F401
  531. def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path):
  532. monkeypatch.chdir(dummy_dist)
  533. distinfo = tmp_path / "dummy_dist.dist-info"
  534. distinfo.mkdir()
  535. (distinfo / "METADATA").write_text("name: helloworld", encoding="utf-8")
  536. # We don't control the metadata. According to PEP-517, "The hook MAY also
  537. # create other files inside this directory, and a build frontend MUST
  538. # preserve".
  539. (distinfo / "FOO").write_text("bar", encoding="utf-8")
  540. bdist_wheel_cmd(bdist_dir=str(tmp_path), dist_info_dir=str(distinfo)).run()
  541. expected = {
  542. "dummy_dist-1.0.dist-info/FOO",
  543. "dummy_dist-1.0.dist-info/RECORD",
  544. }
  545. with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf:
  546. files_found = set(wf.namelist())
  547. # Check that all expected files are there.
  548. assert expected - files_found == set()
  549. # Make sure there is no accidental egg-info bleeding into the wheel.
  550. assert not [path for path in files_found if 'egg-info' in str(path)]
  551. def test_allow_grace_period_parent_directory_license(monkeypatch, tmp_path):
  552. # Motivation: https://github.com/pypa/setuptools/issues/4892
  553. # TODO: Remove this test after deprecation period is over
  554. files = {
  555. "LICENSE.txt": "parent license", # <---- the license files are outside
  556. "NOTICE.txt": "parent notice",
  557. "python": {
  558. "pyproject.toml": cleandoc(
  559. """
  560. [project]
  561. name = "test-proj"
  562. dynamic = ["version"] # <---- testing dynamic will not break
  563. [tool.setuptools.dynamic]
  564. version.file = "VERSION"
  565. """
  566. ),
  567. "setup.cfg": cleandoc(
  568. """
  569. [metadata]
  570. license_files =
  571. ../LICENSE.txt
  572. ../NOTICE.txt
  573. """
  574. ),
  575. "VERSION": "42",
  576. },
  577. }
  578. jaraco.path.build(files, prefix=str(tmp_path))
  579. monkeypatch.chdir(tmp_path / "python")
  580. msg = "Pattern '../.*.txt' cannot contain '..'"
  581. with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
  582. bdist_wheel_cmd().run()
  583. with ZipFile("dist/test_proj-42-py3-none-any.whl") as wf:
  584. files_found = set(wf.namelist())
  585. expected_files = {
  586. "test_proj-42.dist-info/licenses/LICENSE.txt",
  587. "test_proj-42.dist-info/licenses/NOTICE.txt",
  588. }
  589. assert expected_files <= files_found
  590. metadata = wf.read("test_proj-42.dist-info/METADATA").decode("utf8")
  591. assert "License-File: LICENSE.txt" in metadata
  592. assert "License-File: NOTICE.txt" in metadata