comparison _postprocess-sdist.py @ 397:c033f4072c14

FIX: _postprocess-sdist now works for compressed archives and configuration in pyproject.toml. While there: adjusted the Makefile accordingly.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 16 Feb 2026 15:22:37 +0100
parents c19a21180a8f
children 669dd560f21a
comparison
equal deleted inserted replaced
396:1afe2758f802 397:c033f4072c14
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 # :- 2 # :-
3 # SPDX-FileCopyrightText: © 2025 Franz Glasner 3 # SPDX-FileCopyrightText: © 2025-2026 Franz Glasner
4 # SPDX-License-Identifier: BSD-3-Clause 4 # SPDX-License-Identifier: BSD-3-Clause
5 # :- 5 # :-
6 """Postprocress a .tar-sdist to include tests/data with symlinks as symlinks. 6 """Postprocress a .tar.gz-sdist to include tests/data with symlinks as symlinks.
7 7
8 Produce an sdist with all the data in :file:`tests/data/`:: 8 Produce an sdist with all the data in :file:`tests/data/`::
9 9
10 rm -rf dist py_cutils.egg-info 10 rm -rf dist py_cutils.egg-info
11 python setup.py sdist 11 python -m build
12 python _postprocess-sdist.py 12 python _postprocess-sdist.py
13 gzip dist/*.tar
14 13
15 """ 14 """
16 15
17 from __future__ import print_function, absolute_import 16 from __future__ import print_function, absolute_import
18 17
19 try: 18 try:
20 from configparser import ConfigParser 19 import tomllib
21 except ImportError: 20 except ImportError:
22 from ConfigParser import SafeConfigParser as ConfigParser 21 import tomli as tomllib
23 import importlib 22 import importlib
23 import os
24 import io 24 import io
25 import os
26 import tarfile 25 import tarfile
26 import gzip
27 27
28 28
29 def main(): 29 def main():
30 with io.open("setup.cfg", "rt", encoding="utf-8") as cfgfile: 30 with open("pyproject.toml", "rb") as cfgfile:
31 cp = ConfigParser() 31 cfg = tomllib.load(cfgfile)
32 if hasattr(cp, "read_file"): 32 project_name = cfg["project"]["name"]
33 cp.read_file(cfgfile, "setup.cfg") 33 normalized_project_name = project_name.replace("-", "_")
34 project_version = cfg["project"].get("version")
35 if project_version is None:
36 assert "version" in cfg["project"]["dynamic"]
37 project_version = cfg["tool"]["setuptools"]["dynamic"]["version"]
38 if "attr" in project_version:
39 vermodname, dummy, vermodattr = (project_version["attr"]
40 .strip()
41 .rpartition('.'))
42 assert dummy is not None and vermodattr is not None
43 vermod = importlib.import_module(vermodname)
44 project_version = getattr(vermod, vermodattr)
34 else: 45 else:
35 cp.readfp(cfgfile, "setup.cfg") 46 assert False
36 project_name = cp.get("metadata", "name")
37 project_version = cp.get("metadata", "version")
38 if project_version.startswith("attr:"):
39 vermodname, dummy, vermodattr = (project_version[5:]
40 .strip()
41 .rpartition('.'))
42 assert dummy is not None and vermodattr is not None
43 vermod = importlib.import_module(vermodname)
44 project_version = getattr(vermod, vermodattr)
45 elif project_version.startswith("file:"):
46 assert False
47 # 47 #
48 # Compressed tar files cannot be modified by Python: make sure the 48 # PEP 625 requires that sdists are of the form
49 # originally generated archive is uncompressed. 49 # <normalized_project_name>-<project_version>.tar.gz
50 # 50 #
51 assert cp.get("sdist", "formats") == "tar" 51 archive_name = "{}-{}.tar.gz".format(
52 52 normalized_project_name, project_version)
53 archive_name = "{}-{}.tar".format(project_name, project_version) 53 uncompressed_archive_name = "{}-{}.tar".format(
54 normalized_project_name, project_version)
54 archive_path = "dist/" + archive_name 55 archive_path = "dist/" + archive_name
56 uncompressed_archive_path = "dist/" + uncompressed_archive_name
55 assert os.path.isfile(archive_path) 57 assert os.path.isfile(archive_path)
58 assert not os.path.isfile(uncompressed_archive_path)
56 59
57 # the directory within the archive 60 # the directory within the archive
58 archive_path_prefix = "{}-{}".format(project_name, project_version) 61 archive_path_prefix = "{}-{}".format(
62 normalized_project_name, project_version)
59 63
60 egg_directory = "{}.egg-info".format(project_name.replace("-", "_")) 64 egg_directory = "{}.egg-info".format(normalized_project_name)
61 assert os.path.isdir(egg_directory) 65 assert os.path.isdir(egg_directory)
62 sources_txt_path = "{}/SOURCES.txt".format(egg_directory) 66 sources_txt_path = "{}/SOURCES.txt".format(egg_directory)
63 sources_txt_arcname = "{}/{}/SOURCES.txt".format( 67 sources_txt_arcname = "{}/{}/SOURCES.txt".format(
64 archive_path_prefix, 68 archive_path_prefix,
65 egg_directory) 69 egg_directory)
66 70
67 with tarfile.TarFile(archive_path, "r") as tf: 71 # Uncompress
72 with gzip.GzipFile(filename=archive_path, mode="rb") as ca:
73 with open(uncompressed_archive_path, "wb") as uca:
74 while True:
75 data = ca.read(64*1024)
76 if not data:
77 break
78 uca.write(data)
79
80 with tarfile.TarFile(uncompressed_archive_path, "r") as tf:
68 sf = tf.extractfile(sources_txt_arcname) 81 sf = tf.extractfile(sources_txt_arcname)
69 try: 82 try:
70 sources_txt = sf.read() 83 sources_txt = sf.read()
71 finally: 84 finally:
72 sf.close() 85 sf.close()
73 86
74 with tarfile.TarFile(archive_path, "a") as tf: 87 with tarfile.TarFile(uncompressed_archive_path, "a") as tf:
75 arcname = "{}/tests/data".format(archive_path_prefix) 88 arcname = "{}/tests/data".format(archive_path_prefix)
76 try: 89 try:
77 info = tf.getmember(arcname) 90 info = tf.getmember(arcname)
78 except KeyError: 91 except KeyError:
79 pass 92 pass
111 # This adds SOURCES.txt a 2nd time: this effectively overwrites 124 # This adds SOURCES.txt a 2nd time: this effectively overwrites
112 # the "earlier" one. 125 # the "earlier" one.
113 # 126 #
114 tf.addfile(sources_info, sf) 127 tf.addfile(sources_info, sf)
115 128
129 # Compress
130 with open(uncompressed_archive_path, "rb") as uca:
131 with open(archive_path, "wb") as ca:
132 with gzip.GzipFile(filename=uncompressed_archive_name,
133 fileobj=ca,
134 mode="wb") as gzfile:
135 while True:
136 data = uca.read(64*1024)
137 if not data:
138 break
139 gzfile.write(data)
140
116 141
117 def b(buf, encoding="ascii"): 142 def b(buf, encoding="ascii"):
118 if isinstance(buf, bytes): 143 if isinstance(buf, bytes):
119 return buf 144 return buf
120 else: 145 else: