Skip to content

Commit 807cfba

Browse files
henryiiigaborbernatpre-commit-ci[bot]
authored
feat: add --config-json (#916)
Co-authored-by: Bernát Gábor <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent bf54ad0 commit 807cfba

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ available via the command line as `pyproject-build` once installed.
3636
- `--sdist` (`-s`): Produce just an SDist
3737
- `--wheel` (`-w`): Produce just a wheel
3838
- `-C<option>=<value>`: A Config-setting, the PEP 517 way of passing options to a backend. Can be passed multiple times. Matching options will make a list. Note that setuptools has very limited support.
39+
- `--config-json=<value>`: An alternative way to pass in complex config settings as JSON strings. Can't be used with `-C`.
3940
- `--installer`: Pick an installer for the isolated build (`pip` or `uv`).
4041
- `--no-isolation` (`-n`): Disable build isolation.
4142
- `--skip-dependency-check` (`-x`): Disable dependency checking when not isolated; this should be done if some requirements or version ranges are not required for non-isolated builds.
@@ -75,14 +76,7 @@ $ uvx --from build pyproject-build --installer uv
7576

7677
#### cibuildwheel
7778

78-
If you are using [cibuildwheel][], build is integrated and can be use with either (in your `pyproject.toml`):
79-
80-
```toml
81-
[tool.cibuildwheel]
82-
build-frontend = "build"
83-
```
84-
85-
or
79+
If you are using [cibuildwheel][], build is integrated and the default builder in 3.0+. If you want to use `uv` as the installer, you can use:
8680

8781
```toml
8882
[tool.cibuildwheel]

src/build/__main__.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import argparse
66
import contextlib
77
import contextvars
8+
import json
89
import os
910
import platform
1011
import shutil
@@ -385,7 +386,8 @@ def main_parser() -> argparse.ArgumentParser:
385386
choices=_env.INSTALLERS,
386387
help='Python package installer to use (defaults to pip)',
387388
)
388-
parser.add_argument(
389+
config_group = parser.add_mutually_exclusive_group()
390+
config_group.add_argument(
389391
'--config-setting',
390392
'-C',
391393
dest='config_settings',
@@ -395,6 +397,15 @@ def main_parser() -> argparse.ArgumentParser:
395397
'by a space character; use ``--config-setting=--my-setting -C--my-other-setting``',
396398
metavar='KEY[=VALUE]',
397399
)
400+
config_group.add_argument(
401+
'--config-json',
402+
dest='config_json',
403+
help='settings to pass to the backend as a JSON object. '
404+
'This is an alternative to --config-setting that allows complex nested structures. '
405+
'Cannot be used together with --config-setting',
406+
metavar='JSON_STRING',
407+
)
408+
398409
return parser
399410

400411

@@ -414,7 +425,17 @@ def main(cli_args: Sequence[str], prog: str | None = None) -> None:
414425

415426
config_settings = {}
416427

417-
if args.config_settings:
428+
# Handle --config-json
429+
if args.config_json:
430+
try:
431+
config_settings = json.loads(args.config_json)
432+
if not isinstance(config_settings, dict):
433+
_error('--config-json must contain a JSON object (dict), not a list or primitive value')
434+
except json.JSONDecodeError as e:
435+
_error(f'Invalid JSON in --config-json: {e}')
436+
437+
# Handle --config-setting (original logic)
438+
elif args.config_settings:
418439
for arg in args.config_settings:
419440
setting, _, value = arg.partition('=')
420441
if setting not in config_settings:

tests/test_main.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@
9393
[cwd, out, ['wheel'], {'--flag1': 'value', '--flag2': ['other_value', 'extra_value']}, True, False, None],
9494
'build_package_via_sdist',
9595
),
96+
(
97+
['--config-json={"one": 1, "two": [2, 3], "three": {"in": "out"}}'],
98+
[cwd, out, ['wheel'], {'one': 1, 'two': [2, 3], 'three': {'in': 'out'}}, True, False, None],
99+
'build_package_via_sdist',
100+
),
101+
(
102+
['--config-json', '{"outer": {"inner": {"deeper": 2}}}'],
103+
[cwd, out, ['wheel'], {'outer': {'inner': {'deeper': 2}}}, True, False, None],
104+
'build_package_via_sdist',
105+
),
106+
(
107+
['--config-json', '{}'],
108+
[cwd, out, ['wheel'], {}, True, False, None],
109+
'build_package_via_sdist',
110+
),
96111
],
97112
)
98113
def test_parse_args(mocker, cli_args, build_args, hook):
@@ -177,6 +192,22 @@ def test_build_no_isolation_with_check_deps(mocker, package_test_flit, missing_d
177192
error.assert_called_with('Missing dependencies:' + output)
178193

179194

195+
@pytest.mark.parametrize(
196+
['cli_args', 'err_msg'],
197+
[
198+
(['-Cone=1', '--config-json={"two": 2}'], 'not allowed with argument'),
199+
(['--config-json={"two": 2'], 'Invalid JSON in --config-json'),
200+
(['--config-json=[1]'], '--config-json must contain a JSON object'),
201+
],
202+
)
203+
def test_config_json_errors(cli_args, err_msg, capsys):
204+
with pytest.raises(SystemExit):
205+
build.__main__.main(cli_args)
206+
207+
outerr = capsys.readouterr()
208+
assert err_msg in outerr.out or err_msg in outerr.err
209+
210+
180211
@pytest.mark.isolated
181212
def test_build_raises_build_exception(mocker, package_test_flit):
182213
mocker.patch('build.ProjectBuilder.get_requires_for_build', side_effect=build.BuildException)

0 commit comments

Comments
 (0)