Skip to content

Commit 1a95ecc

Browse files
authored
Add minimal CLI (#42)
1 parent 994c8b4 commit 1a95ecc

File tree

4 files changed

+243
-45
lines changed

4 files changed

+243
-45
lines changed

src/pyproject_api/__main__.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import os
5+
import pathlib
6+
import sys
7+
8+
from ._frontend import EditableResult, SdistResult, WheelResult
9+
from ._via_fresh_subprocess import SubprocessFrontend
10+
11+
12+
def main_parser() -> argparse.ArgumentParser:
13+
parser = argparse.ArgumentParser(
14+
description=(
15+
"A pyproject.toml-based build frontend. "
16+
"This is mainly useful for debugging PEP-517 backends. "
17+
"This frontend will not do things like install required build dependencies."
18+
),
19+
)
20+
parser.add_argument(
21+
"srcdir",
22+
type=pathlib.Path,
23+
nargs="?",
24+
default=pathlib.Path.cwd(),
25+
help="source directory (defaults to current directory)",
26+
)
27+
parser.add_argument(
28+
"--sdist",
29+
"-s",
30+
dest="distributions",
31+
action="append_const",
32+
const="sdist",
33+
default=[],
34+
help="build a source distribution",
35+
)
36+
parser.add_argument(
37+
"--wheel",
38+
"-w",
39+
dest="distributions",
40+
action="append_const",
41+
const="wheel",
42+
help="build a wheel distribution",
43+
)
44+
parser.add_argument(
45+
"--editable",
46+
"-e",
47+
dest="distributions",
48+
action="append_const",
49+
const="editable",
50+
help="build an editable wheel distribution",
51+
)
52+
parser.add_argument(
53+
"--outdir",
54+
"-o",
55+
type=pathlib.Path,
56+
help=f"output directory (defaults to {{srcdir}}{os.sep}dist)",
57+
)
58+
return parser
59+
60+
61+
def main(argv: list[str]) -> None:
62+
parser = main_parser()
63+
args = parser.parse_args(argv)
64+
65+
outdir = args.outdir or args.srcdir / "dist"
66+
# we intentionally do not build editable distributions by default
67+
distributions = args.distributions or ["sdist", "wheel"]
68+
69+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(args.srcdir)[:-1])
70+
res: SdistResult | WheelResult | EditableResult
71+
72+
if "sdist" in distributions:
73+
print("Building sdist...")
74+
res = frontend.build_sdist(outdir)
75+
print(res.out)
76+
print(res.err, file=sys.stderr)
77+
78+
if "wheel" in distributions:
79+
print("Building wheel...")
80+
res = frontend.build_wheel(outdir)
81+
print(res.out)
82+
print(res.err, file=sys.stderr)
83+
84+
if "editable" in distributions:
85+
print("Building editable wheel...")
86+
res = frontend.build_editable(outdir)
87+
print(res.out)
88+
print(res.err, file=sys.stderr)
89+
90+
91+
if __name__ == "__main__":
92+
main(sys.argv[1:])

tests/test_frontend.py

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ def test_missing_backend(local_builder: Callable[[str], Path]) -> None:
2727
tmp_path = local_builder("")
2828
toml = tmp_path / "pyproject.toml"
2929
toml.write_text('[build-system]\nrequires=[]\nbuild-backend = "build_tester"')
30-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
30+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
3131
with pytest.raises(BackendFailed) as context:
32-
fronted.build_wheel(tmp_path / "wheel")
32+
frontend.build_wheel(tmp_path / "wheel")
3333
exc = context.value
3434
assert exc.exc_type == "RuntimeError"
3535
assert exc.code == 1
@@ -40,10 +40,10 @@ def test_missing_backend(local_builder: Callable[[str], Path]) -> None:
4040
@pytest.mark.parametrize("cmd", ["build_wheel", "build_sdist"])
4141
def test_missing_required_cmd(cmd: str, local_builder: Callable[[str], Path]) -> None:
4242
tmp_path = local_builder("")
43-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
43+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
4444

4545
with pytest.raises(BackendFailed) as context:
46-
getattr(fronted, cmd)(tmp_path)
46+
getattr(frontend, cmd)(tmp_path)
4747
exc = context.value
4848
assert f"has no attribute '{cmd}'" in exc.exc_msg
4949
assert exc.exc_type == "MissingCommand"
@@ -67,14 +67,14 @@ def demo_pkg_inline() -> Path:
6767

6868

6969
def test_backend_no_prepare_wheel(tmp_path: Path, demo_pkg_inline: Path) -> None:
70-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
71-
result = fronted.prepare_metadata_for_build_wheel(tmp_path)
70+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
71+
result = frontend.prepare_metadata_for_build_wheel(tmp_path)
7272
assert result.metadata.name == "demo_pkg_inline-1.0.0.dist-info"
7373

7474

7575
def test_backend_build_sdist_demo_pkg_inline(tmp_path: Path, demo_pkg_inline: Path) -> None:
76-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
77-
result = fronted.build_sdist(sdist_directory=tmp_path)
76+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
77+
result = frontend.build_sdist(sdist_directory=tmp_path)
7878
assert result.sdist == tmp_path / "demo_pkg_inline-1.0.0.tar.gz"
7979

8080

@@ -97,8 +97,8 @@ def get_requires_for_build_sdist(self, config_settings=None):
9797
build.mkdir()
9898
(build / "__init__.py").write_text("")
9999
(build / "api.py").write_text(dedent(api))
100-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
101-
result = fronted.get_requires_for_build_sdist()
100+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
101+
result = frontend.get_requires_for_build_sdist()
102102
for left, right in zip(result.requires, (Requirement("a"),)):
103103
assert isinstance(left, Requirement)
104104
assert str(left) == str(right)
@@ -107,18 +107,18 @@ def get_requires_for_build_sdist(self, config_settings=None):
107107
@pytest.mark.parametrize("of_type", ["wheel", "sdist"])
108108
def test_get_requires_for_build_missing(of_type: str, local_builder: Callable[[str], Path]) -> None:
109109
tmp_path = local_builder("")
110-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
111-
result = getattr(fronted, f"get_requires_for_build_{of_type}")()
110+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
111+
result = getattr(frontend, f"get_requires_for_build_{of_type}")()
112112
assert result.requires == ()
113113

114114

115115
@pytest.mark.parametrize("of_type", ["sdist", "wheel"])
116116
def test_bad_return_type_get_requires_for_build(of_type: str, local_builder: Callable[[str], Path]) -> None:
117117
tmp_path = local_builder(f"def get_requires_for_build_{of_type}(config_settings=None): return 1")
118-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
118+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
119119

120120
with pytest.raises(BackendFailed) as context:
121-
getattr(fronted, f"get_requires_for_build_{of_type}")()
121+
getattr(frontend, f"get_requires_for_build_{of_type}")()
122122

123123
exc = context.value
124124
msg = f"'get_requires_for_build_{of_type}' on 'build_tester' returned 1 but expected type 'list of string'"
@@ -128,10 +128,10 @@ def test_bad_return_type_get_requires_for_build(of_type: str, local_builder: Cal
128128

129129
def test_bad_return_type_build_sdist(local_builder: Callable[[str], Path]) -> None:
130130
tmp_path = local_builder("def build_sdist(sdist_directory, config_settings=None): return 1")
131-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
131+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
132132

133133
with pytest.raises(BackendFailed) as context:
134-
fronted.build_sdist(tmp_path)
134+
frontend.build_sdist(tmp_path)
135135

136136
exc = context.value
137137
assert exc.exc_msg == f"'build_sdist' on 'build_tester' returned 1 but expected type {str!r}"
@@ -141,10 +141,10 @@ def test_bad_return_type_build_sdist(local_builder: Callable[[str], Path]) -> No
141141
def test_bad_return_type_build_wheel(local_builder: Callable[[str], Path]) -> None:
142142
txt = "def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): return 1"
143143
tmp_path = local_builder(txt)
144-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
144+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
145145

146146
with pytest.raises(BackendFailed) as context:
147-
fronted.build_wheel(tmp_path)
147+
frontend.build_wheel(tmp_path)
148148

149149
exc = context.value
150150
assert exc.exc_msg == f"'build_wheel' on 'build_tester' returned 1 but expected type {str!r}"
@@ -153,10 +153,10 @@ def test_bad_return_type_build_wheel(local_builder: Callable[[str], Path]) -> No
153153

154154
def test_bad_return_type_prepare_metadata_for_build_wheel(local_builder: Callable[[str], Path]) -> None:
155155
tmp_path = local_builder("def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): return 1")
156-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
156+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
157157

158158
with pytest.raises(BackendFailed) as context:
159-
fronted.prepare_metadata_for_build_wheel(tmp_path / "meta")
159+
frontend.prepare_metadata_for_build_wheel(tmp_path / "meta")
160160

161161
exc = context.value
162162
assert exc.exc_type == "TypeError"
@@ -165,21 +165,21 @@ def test_bad_return_type_prepare_metadata_for_build_wheel(local_builder: Callabl
165165

166166
def test_prepare_metadata_for_build_wheel_meta_is_root(local_builder: Callable[[str], Path]) -> None:
167167
tmp_path = local_builder("def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): return 1")
168-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
168+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
169169

170170
with pytest.raises(RuntimeError) as context:
171-
fronted.prepare_metadata_for_build_wheel(tmp_path)
171+
frontend.prepare_metadata_for_build_wheel(tmp_path)
172172

173173
assert str(context.value) == f"the project root and the metadata directory can't be the same {tmp_path}"
174174

175175

176176
def test_no_wheel_prepare_metadata_for_build_wheel(local_builder: Callable[[str], Path]) -> None:
177177
txt = "def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): return 'out'"
178178
tmp_path = local_builder(txt)
179-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
179+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
180180

181181
with pytest.raises(RuntimeError, match="missing wheel file return by backed *"):
182-
fronted.prepare_metadata_for_build_wheel(tmp_path / "meta")
182+
frontend.prepare_metadata_for_build_wheel(tmp_path / "meta")
183183

184184

185185
def test_bad_wheel_prepare_metadata_for_build_wheel(local_builder: Callable[[str], Path]) -> None:
@@ -196,10 +196,10 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
196196
return path.name
197197
"""
198198
tmp_path = local_builder(txt)
199-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
199+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(tmp_path)[:-1])
200200

201201
with pytest.raises(RuntimeError, match="no .dist-info found inside generated wheel*"):
202-
fronted.prepare_metadata_for_build_wheel(tmp_path / "meta")
202+
frontend.prepare_metadata_for_build_wheel(tmp_path / "meta")
203203

204204

205205
def test_create_no_pyproject(tmp_path: Path) -> None:
@@ -217,8 +217,8 @@ def test_create_no_pyproject(tmp_path: Path) -> None:
217217
def test_backend_get_requires_for_build_editable(demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
218218
monkeypatch.setenv("HAS_REQUIRES_EDITABLE", "1")
219219
monkeypatch.delenv("REQUIRES_EDITABLE_BAD_RETURN", raising=False)
220-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
221-
result = fronted.get_requires_for_build_editable()
220+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
221+
result = frontend.get_requires_for_build_editable()
222222
assert [str(i) for i in result.requires] == ["editables"]
223223
assert isinstance(result.requires[0], Requirement)
224224
assert " get_requires_for_build_editable " in result.out
@@ -227,8 +227,8 @@ def test_backend_get_requires_for_build_editable(demo_pkg_inline: Path, monkeypa
227227

228228
def test_backend_get_requires_for_build_editable_miss(demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
229229
monkeypatch.delenv("HAS_REQUIRES_EDITABLE", raising=False)
230-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
231-
result = fronted.get_requires_for_build_editable()
230+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
231+
result = frontend.get_requires_for_build_editable()
232232
assert not result.requires
233233
assert not result.out
234234
assert not result.err
@@ -237,9 +237,9 @@ def test_backend_get_requires_for_build_editable_miss(demo_pkg_inline: Path, mon
237237
def test_backend_get_requires_for_build_editable_bad(demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
238238
monkeypatch.setenv("HAS_REQUIRES_EDITABLE", "1")
239239
monkeypatch.setenv("REQUIRES_EDITABLE_BAD_RETURN", "1")
240-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
240+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
241241
with pytest.raises(BackendFailed) as context:
242-
fronted.get_requires_for_build_editable()
242+
frontend.get_requires_for_build_editable()
243243
exc = context.value
244244
assert exc.code is None
245245
assert not exc.err
@@ -252,8 +252,8 @@ def test_backend_get_requires_for_build_editable_bad(demo_pkg_inline: Path, monk
252252
def test_backend_prepare_editable(tmp_path: Path, demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
253253
monkeypatch.setenv("HAS_PREPARE_EDITABLE", "1")
254254
monkeypatch.delenv("PREPARE_EDITABLE_BAD", raising=False)
255-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
256-
result = fronted.prepare_metadata_for_build_editable(tmp_path)
255+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
256+
result = frontend.prepare_metadata_for_build_editable(tmp_path)
257257
assert result.metadata.name == "demo_pkg_inline-1.0.0.dist-info"
258258
assert " prepare_metadata_for_build_editable " in result.out
259259
assert " build_editable " not in result.out
@@ -263,8 +263,8 @@ def test_backend_prepare_editable(tmp_path: Path, demo_pkg_inline: Path, monkeyp
263263
def test_backend_prepare_editable_miss(tmp_path: Path, demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
264264
monkeypatch.delenv("HAS_PREPARE_EDITABLE", raising=False)
265265
monkeypatch.delenv("BUILD_EDITABLE_BAD", raising=False)
266-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
267-
result = fronted.prepare_metadata_for_build_editable(tmp_path)
266+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
267+
result = frontend.prepare_metadata_for_build_editable(tmp_path)
268268
assert result.metadata.name == "demo_pkg_inline-1.0.0.dist-info"
269269
assert " prepare_metadata_for_build_editable " not in result.out
270270
assert " build_editable " in result.out
@@ -274,9 +274,9 @@ def test_backend_prepare_editable_miss(tmp_path: Path, demo_pkg_inline: Path, mo
274274
def test_backend_prepare_editable_bad(tmp_path: Path, demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
275275
monkeypatch.setenv("HAS_PREPARE_EDITABLE", "1")
276276
monkeypatch.setenv("PREPARE_EDITABLE_BAD", "1")
277-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
277+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
278278
with pytest.raises(BackendFailed) as context:
279-
fronted.prepare_metadata_for_build_editable(tmp_path)
279+
frontend.prepare_metadata_for_build_editable(tmp_path)
280280
exc = context.value
281281
assert exc.code is None
282282
assert not exc.err
@@ -288,18 +288,18 @@ def test_backend_prepare_editable_bad(tmp_path: Path, demo_pkg_inline: Path, mon
288288

289289
def test_backend_build_editable(tmp_path: Path, demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
290290
monkeypatch.delenv("BUILD_EDITABLE_BAD", raising=False)
291-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
292-
result = fronted.build_editable(tmp_path)
291+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
292+
result = frontend.build_editable(tmp_path)
293293
assert result.wheel.name == "demo_pkg_inline-1.0.0-py3-none-any.whl"
294294
assert " build_editable " in result.out
295295
assert not result.err
296296

297297

298298
def test_backend_build_editable_bad(tmp_path: Path, demo_pkg_inline: Path, monkeypatch: pytest.MonkeyPatch) -> None:
299299
monkeypatch.setenv("BUILD_EDITABLE_BAD", "1")
300-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
300+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
301301
with pytest.raises(BackendFailed) as context:
302-
fronted.build_editable(tmp_path)
302+
frontend.build_editable(tmp_path)
303303
exc = context.value
304304
assert exc.code is None
305305
assert not exc.err
@@ -310,9 +310,9 @@ def test_backend_build_editable_bad(tmp_path: Path, demo_pkg_inline: Path, monke
310310

311311

312312
def test_can_build_on_python_2(demo_pkg_inline: Path, tmp_path: Path) -> None:
313-
fronted = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
313+
frontend = SubprocessFrontend(*SubprocessFrontend.create_args_from_folder(demo_pkg_inline)[:-1])
314314
env = session_via_cli(["-p", "2.7", str(tmp_path / "venv")])
315315
env.run()
316-
fronted.executable = str(env.creator.exe)
316+
frontend.executable = str(env.creator.exe)
317317

318-
fronted.build_sdist(tmp_path)
318+
frontend.build_sdist(tmp_path)

0 commit comments

Comments
 (0)