fix: config settings expansion issue (#2886)

This commit is contained in:
Henry Schreiner
2026-06-02 07:57:09 -04:00
committed by GitHub
parent f01c1955a9
commit bb4ee99896
3 changed files with 42 additions and 2 deletions
+11 -1
View File
@@ -62,7 +62,17 @@ def _split_config_settings(config_settings: str) -> list[str]:
def prepare_config_settings(config_settings: str, *, project: PathOrStr, package: PathOrStr) -> str:
return prepare_command(config_settings, project=project, package=package)
# Substitute the {project}/{package} placeholders on each already-split
# token rather than on the raw string. A substituted path may contain
# spaces or backslashes (e.g. a Windows `{package}` path), and the result
# is later re-parsed with shlex.split (in _split_config_settings /
# parse_config_settings) — substituting on the whole string would let
# those characters be reinterpreted, splitting one setting into several or
# eating backslashes. shlex.join re-quotes each token so the round-trip is
# lossless.
settings = shlex.split(config_settings)
prepared = [prepare_command(setting, project=project, package=package) for setting in settings]
return shlex.join(prepared)
# Based on build.__main__.main.
+6 -1
View File
@@ -276,7 +276,12 @@ def setup_env(
call(*pip, "install", *pb.build_system_requires, env=build_env)
requires_for_build = pb.get_requires_for_build(
"wheel", parse_config_settings(build_options.config_settings)
"wheel",
parse_config_settings(
prepare_config_settings(
build_options.config_settings, project=".", package=build_options.package_dir
)
),
)
if requires_for_build:
call(*pip, "install", *requires_for_build, env=build_env)
+25
View File
@@ -13,7 +13,9 @@ from cibuildwheel import errors
from cibuildwheel.bashlex_eval import local_environment_executor
from cibuildwheel.frontend import (
BuildFrontendConfig,
_split_config_settings,
get_build_frontend_extra_flags,
parse_config_settings,
prepare_config_settings,
)
from cibuildwheel.logger import Logger
@@ -608,6 +610,29 @@ def test_prepare_config_settings(config_settings: str, expected: str) -> None:
)
@pytest.mark.parametrize(
("project", "package"),
[
(R"C:\Users\John Doe\project", R"C:\Users\John Doe\project\pkg"),
("/home/me/my project", "/home/me/my project/pkg"),
],
)
def test_prepare_config_settings_special_chars(project: str, package: str) -> None:
# Paths with spaces or backslashes must survive the round-trip through the
# downstream shlex.split intact (regression test for the {package}/{project}
# substitution being mangled by re-parsing).
config_settings = "setup-args=--cross-file={package}/cross.ini other-setting={project}"
prepared = prepare_config_settings(config_settings, project=project, package=package)
assert _split_config_settings(prepared) == [
f"-Csetup-args=--cross-file={package}/cross.ini",
f"-Cother-setting={project}",
]
assert parse_config_settings(prepared) == {
"setup-args": f"--cross-file={package}/cross.ini",
"other-setting": project,
}
@pytest.mark.parametrize(
("definition", "expected_args"),
[