-
Notifications
You must be signed in to change notification settings - Fork 14
/
build_wheel.py
165 lines (137 loc) · 4.89 KB
/
build_wheel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python
"""Build the wheel.
The number of commits since the last major/minor version change is automatically
added to the version of the package as the patch version.
"""
from __future__ import annotations
import sys
from pathlib import Path
import re
import tomllib
from e3.main import Main
from e3.os.process import Run
from e3.log import getLogger
logger = getLogger("build_wheel")
def run(cmd: list[str], cwd: Path, fail_ok: bool | None = None) -> str:
"""Run a command and check the status.
:param cmd: the command
:param cwd: the directory where to run the command
:param fail_ok: allow the command to fail
:return: the output of the command
"""
p = Run(cmd, cwd=cwd)
if p.status != 0 and not fail_ok:
logger.error(p.out)
sys.exit(1)
output = p.out
assert output is not None
return output
def main() -> None:
"""Entrypoint."""
main = Main()
parser = main.argument_parser
parser.description = "Build the wheel"
parser.add_argument(
"--project",
default="pyproject.toml",
help="Path to a Python project or pyproject.toml file",
)
parser.add_argument("--dry-run", action="store_true", help="Do not build the wheel")
main.parse_args()
assert main.args
project = Path(main.args.project)
if project.is_dir():
project = project / "pyproject.toml"
logger.debug(f"Project dir: {project.parent}")
if not project.is_file():
logger.error(f"{project} is not a file")
sys.exit(1)
# Find the path to version file
with open(project, "rb") as f:
version_config = (
tomllib.load(f)
.get("tool", {})
.get("setuptools", {})
.get("dynamic", {})
.get("version")
)
if version_config is None:
logger.error("Missing the version key in [tool.setuptools.dynamic] section")
sys.exit(1)
version_path = (
version_config["file"]
if "file" in version_config
else f'src/{version_config["attr"].replace(".", "/")}.py'
)
logger.debug(f"Version path: {version_path}")
# Read the version
root_dir = project.parent
version_abspath = root_dir / version_path
with open(version_abspath) as f:
version_content = f.read()
# Extract the <major>.<minor>(.<patch>)? part.
# We will replace the patch version by the number of commits since the most
# recent tagged version
version_pattern = r"(?P<version>(?P<major>\d+)\.(?P<minor>\d+)(\.\w+)?)"
match = re.search(version_pattern, version_content)
if not match:
logger.error(f"No <major>.<minor>(.<patch>)? version found in {version_path}")
sys.exit(1)
version_major = match.group("major")
version_minor = match.group("minor")
version = match.group("version")
logger.debug(f"Version: {version}")
# Need to unshallow the clone to get all commits.
# That command can fail for an already complete clone
run(["git", "fetch", "--unshallow", "--tags"], cwd=root_dir, fail_ok=True)
# Walk through the commits on the main branch only that modified the version file
output = run(
["git", "log", "--first-parent", "--format=format:%H", version_path],
cwd=root_dir,
)
# Find the SHA when the major/minor version changed
previous_commit_sha = "HEAD"
for commit_sha in output.strip().splitlines():
output = run(["git", "show", f"{commit_sha}:{version_path}"], cwd=root_dir)
match = re.search(version_pattern, output)
if (
not match
or version_major != match.group("major")
or version_minor != match.group("minor")
):
logger.debug(f"Different version found at commit {commit_sha}")
break
previous_commit_sha = commit_sha
# Count the number of commits since that SHA.
output = run(
["git", "rev-list", f"{previous_commit_sha}..HEAD", "--count"],
cwd=root_dir,
)
build_version = f"{version_major}.{version_minor}.{output.strip()}"
logger.info(f"{version_major}.{version_minor} -> {build_version}")
if not main.args.dry_run:
# Replace the version in the file
with open(version_abspath, "w") as f:
f.write(version_content.replace(version, build_version))
try:
# Build the wheel
run(
[
sys.executable,
"-m",
"pip",
"wheel",
".",
"-q",
"--no-deps",
"-C--python-tag=py3",
"-w",
"build",
],
cwd=root_dir,
)
finally:
# Revert change to version file
run(["git", "restore", version_path], cwd=root_dir, fail_ok=True)
if __name__ == "__main__":
main()