fix quoting issue

This commit is contained in:
Richard Ward
2025-12-19 18:29:40 +00:00
parent 83e8fd6d1a
commit 986908a058

View File

@@ -7,6 +7,11 @@ Design goals:
- Explicit argv execution throughout.
- Where practical, allow forwarding extra arguments to underlying tools.
- Keep Windows/MSVC "one-liner" behavior via cmd.exe only when required.
NOTE (Windows quoting):
cmd.exe has special/quirky parsing rules for /c when the command contains quotes.
We use: cmd.exe /d /s /c ""call "<bat>" <arch> && <then...>""
The doubled quotes at both ends are intentional and required for robust behavior.
"""
from __future__ import annotations
@@ -21,7 +26,6 @@ from typing import Iterable, Sequence
IS_WINDOWS = platform.system() == "Windows"
# Generator presets and default build dirs
DEFAULT_GENERATOR = "ninja-msvc" if IS_WINDOWS else "ninja"
GENERATOR_DEFAULT_DIR = {
"vs": "build",
@@ -43,7 +47,6 @@ DEFAULT_VCVARSALL = (
def _print_cmd(argv: Sequence[str]) -> None:
# Use subprocess.list2cmdline for Windows-friendly rendering.
if IS_WINDOWS:
rendered = subprocess.list2cmdline(list(argv))
else:
@@ -52,11 +55,12 @@ def _print_cmd(argv: Sequence[str]) -> None:
def _sh_quote(s: str) -> str:
# Minimal POSIX shell quoting for display-only.
if not s:
return "''"
safe = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"._-/:@=+")
safe = set(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
"._-/:@=+"
)
if all(c in safe for c in s):
return s
return "'" + s.replace("'", "'\"'\"'") + "'"
@@ -71,16 +75,13 @@ def run_argvs(argvs: Iterable[Sequence[str]], dry_run: bool) -> None:
def _as_build_dir(path_str: str | None, fallback: str) -> str:
if path_str:
return path_str
return fallback
return path_str or fallback
def dependencies(args: argparse.Namespace) -> None:
cmd_detect = ["conan", "profile", "detect", "-f"]
cmd_install = ["conan", "install", ".", "-of", "build", "-b", "missing"]
# Allow forward of arbitrary conan install args (e.g. -s, -o, -c).
if args.conan_install_args:
cmd_install.extend(args.conan_install_args)
@@ -98,7 +99,6 @@ def configure(args: argparse.Namespace) -> None:
if generator == "vs":
cmake_args.extend(["-G", CMAKE_GENERATOR["vs"]])
# Multi-config generators typically ignore CMAKE_BUILD_TYPE.
else:
cmake_args.extend(["-G", CMAKE_GENERATOR[generator]])
cmake_args.append(f"-DCMAKE_BUILD_TYPE={args.build_type}")
@@ -118,7 +118,6 @@ def build(args: argparse.Namespace) -> None:
if args.target:
cmd.extend(["--target", args.target])
# Forward extra args to the underlying build tool after "--".
if args.build_tool_args:
cmd.append("--")
cmd.extend(args.build_tool_args)
@@ -126,15 +125,27 @@ def build(args: argparse.Namespace) -> None:
run_argvs([cmd], args.dry_run)
def _cmd_one_liner(call_parts: Sequence[str], then_parts: Sequence[str]) -> list[str]:
def _cmd_quote_arg_for_display(argv: Sequence[str]) -> str:
# Display-only: how this argv might look as a single command line.
return subprocess.list2cmdline(list(argv))
def _cmd_one_liner_vcvars_then(
bat: str,
arch: str,
then_parts: Sequence[str],
) -> list[str]:
"""
Build: cmd.exe /c "call <call_parts...> && <then_parts...>"
Uses list2cmdline to safely quote for cmd.exe.
Robust cmd.exe invocation for:
call "<bat>" <arch> && <then...>
Uses cmd.exe quote rules:
cmd.exe /d /s /c ""call "<bat>" <arch> && <then...>""
"""
call_str = "call " + subprocess.list2cmdline(list(call_parts))
then_str = subprocess.list2cmdline(list(then_parts))
inner = f"{call_str} && {then_str}"
return ["cmd.exe", "/c", inner]
then_cmdline = _cmd_quote_arg_for_display(then_parts)
inner = f'call "{bat}" {arch} && {then_cmdline}'
wrapped = f'""{inner}""'
return ["cmd.exe", "/d", "/s", "/c", wrapped]
def msvc_quick(args: argparse.Namespace) -> None:
@@ -144,8 +155,6 @@ def msvc_quick(args: argparse.Namespace) -> None:
bat = args.bat_path or DEFAULT_VCVARSALL
arch = args.arch or "x64"
# Default action: build (mirrors the README-style one-liner)
# Users can override the "then" command via: msvc-quick -- <command...>
if args.then_command:
then_cmd = list(args.then_command)
else:
@@ -159,7 +168,7 @@ def msvc_quick(args: argparse.Namespace) -> None:
then_cmd.append("--")
then_cmd.extend(args.build_tool_args)
cmd = _cmd_one_liner([bat, arch], then_cmd)
cmd = _cmd_one_liner_vcvars_then(bat, arch, then_cmd)
run_argvs([cmd], args.dry_run)
@@ -249,7 +258,7 @@ def main() -> int:
msvc = subparsers.add_parser(
"msvc-quick",
help="run a VS Developer Prompt call + follow-on command (README one-liner style)",
help="run a VS env setup + follow-on command (README one-liner style)",
)
msvc.add_argument(
"--bat-path",