mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## Why Windows libffi builds invoke tools by name, and Cygwin provides a different `link.exe` from MSVC. Inherited search paths can select unintended tools or SDK inputs. ## What changed Add optional `--windows-build-inputs <json>` to `third_party/voice/build_native.py` to select installed tools and SDK directories for Windows MSVC targets. Validate the target, tool paths, architecture-specific assembler, SDK directories, and agreement with CLI tool arguments. Build the search path from the selection with MSVC ahead of Cygwin, reject shadowed tools, replace inherited `INCLUDE` and `LIB`, and remove `LIBPATH`. Retain the selection in build receipts. Builds without the option continue using the normal Visual Studio environment. ## Testing Add tests for linker precedence and subprocess selection, invalid or mismatched inputs, shadowed SDK tools, environment replacement, and receipt recording. GitOrigin-RevId: c80a6d9eb29556157ba1316d62c9b5083f4038b9
149 lines
5.5 KiB
Python
149 lines
5.5 KiB
Python
"""Exercise explicit Windows tool selection and rejection before native builds."""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from windows_build_inputs import build_environment
|
|
|
|
|
|
class WindowsInputsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory(prefix="voice selected tools ")
|
|
self.addCleanup(temporary.cleanup)
|
|
self.root = Path(temporary.name)
|
|
self.target = "x86_64-pc-windows-msvc"
|
|
self.selected = {}
|
|
for directory, names in {
|
|
"msvc": [
|
|
"cl.exe",
|
|
"link.exe",
|
|
"lib.exe",
|
|
"dumpbin.exe",
|
|
"ml64.exe",
|
|
"nmake.exe",
|
|
],
|
|
"sdk": ["rc.exe", "mt.exe"],
|
|
"cygwin": [
|
|
"bash.exe",
|
|
"cygpath.exe",
|
|
"automake-1.18",
|
|
"make.exe",
|
|
"link.exe",
|
|
],
|
|
"cmake": ["cmake.exe"],
|
|
"pkgconf": ["pkgconf.exe"],
|
|
"Windows/System32": ["cmd.exe"],
|
|
}.items():
|
|
parent = self.root / directory
|
|
parent.mkdir(parents=True)
|
|
for name in names:
|
|
(parent / name).touch()
|
|
self.tools = {
|
|
"cc": self.root / "msvc/cl.exe",
|
|
"cxx": self.root / "msvc/cl.exe",
|
|
"link": self.root / "msvc/link.exe",
|
|
"lib": self.root / "msvc/lib.exe",
|
|
"dumpbin": self.root / "msvc/dumpbin.exe",
|
|
"assembler": self.root / "msvc/ml64.exe",
|
|
"bootstrap_make": self.root / "msvc/nmake.exe",
|
|
"rc": self.root / "sdk/rc.exe",
|
|
"mt": self.root / "sdk/mt.exe",
|
|
"make": self.root / "cygwin/make.exe",
|
|
"shell": self.root / "cygwin/bash.exe",
|
|
"cygpath": self.root / "cygwin/cygpath.exe",
|
|
"automake": self.root / "cygwin/automake-1.18",
|
|
"cmake": self.root / "cmake/cmake.exe",
|
|
"pkg_config": self.root / "pkgconf/pkgconf.exe",
|
|
"python": Path(sys.executable),
|
|
}
|
|
self.document = {
|
|
"schemaVersion": 1,
|
|
"target": self.target,
|
|
"tools": {key: str(value) for key, value in self.tools.items()},
|
|
"systemRoot": str(self.root / "Windows"),
|
|
"INCLUDE": [str(self.root / "sdk")],
|
|
"LIB": [str(self.root / "msvc")],
|
|
}
|
|
self.path = self.root / "inputs.json"
|
|
|
|
def environment(self):
|
|
self.path.write_text(json.dumps(self.document), encoding="utf-8")
|
|
return build_environment(self.path, self.target, self.selected)[0]
|
|
|
|
def test_msvc_link_precedes_same_named_cygwin_tool(self):
|
|
environment = self.environment()
|
|
first = next(
|
|
Path(directory) / "link.exe"
|
|
for directory in environment["PATH"].split(os.pathsep)
|
|
if (Path(directory) / "link.exe").exists()
|
|
)
|
|
self.assertEqual(first, self.tools["link"])
|
|
self.assertEqual(
|
|
{name: environment[name] for name in ("INCLUDE", "LIB", "COMSPEC")},
|
|
{
|
|
"INCLUDE": str(self.root / "sdk"),
|
|
"LIB": str(self.root / "msvc"),
|
|
"COMSPEC": str(self.root / "Windows/System32/cmd.exe"),
|
|
},
|
|
)
|
|
|
|
@unittest.skipIf(os.name == "nt", "Portable shell subprocess selection probe")
|
|
def test_subprocess_uses_selected_tool_without_ambient_path(self):
|
|
# Execute the name upstream uses, with a conflicting Cygwin executable.
|
|
for path, text in (
|
|
(self.tools["link"], "msvc"),
|
|
(self.root / "cygwin/link.exe", "cygwin"),
|
|
):
|
|
path.write_text(f"#!/bin/sh\nprintf '{text}'\n")
|
|
path.chmod(0o755)
|
|
result = subprocess.run(
|
|
["link.exe"],
|
|
env=self.environment(),
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
self.assertEqual(result.stdout, "msvc")
|
|
|
|
def test_missing_or_wrong_architecture_assembler_is_rejected(self):
|
|
self.tools["assembler"].unlink()
|
|
with self.assertRaisesRegex(ValueError, "assembler"):
|
|
self.environment()
|
|
self.tools["assembler"].touch()
|
|
self.target = "aarch64-pc-windows-msvc"
|
|
self.document["target"] = self.target
|
|
with self.assertRaisesRegex(ValueError, "assembler"):
|
|
self.environment()
|
|
arm = self.tools["assembler"].with_name("armasm64.exe")
|
|
arm.touch()
|
|
self.document["tools"]["assembler"] = str(arm)
|
|
self.environment()
|
|
|
|
def test_shadowed_sdk_tool_is_rejected(self):
|
|
shutil.copyfile(self.tools["rc"], self.root / "msvc/rc.exe")
|
|
with self.assertRaisesRegex(ValueError, "shadowed: rc"):
|
|
self.environment()
|
|
|
|
def test_cli_tool_must_match_recorded_selection(self):
|
|
self.selected["cc"] = self.tools["cmake"]
|
|
with self.assertRaisesRegex(ValueError, "disagrees with --cc"):
|
|
self.environment()
|
|
|
|
def test_missing_sdk_and_relative_paths_are_rejected(self):
|
|
for value in ([], ["relative"], [str(self.root / "missing")]):
|
|
with self.subTest(value=value):
|
|
self.document["INCLUDE"] = value
|
|
with self.assertRaises(ValueError):
|
|
self.environment()
|
|
|
|
def test_target_mismatch_cannot_reuse_other_inputs(self):
|
|
self.document["target"] = "aarch64-pc-windows-msvc"
|
|
with self.assertRaisesRegex(ValueError, "target"):
|
|
self.environment()
|