Resolve bundled Windows helpers through bin junctions (#39649)

## Why

Installer `bin` directories can be junctions, so looking for bundled helpers
relative to the apparent executable path can miss the package's
`codex-resources` directory.

## What changed

Retry bundled executable lookup from the canonical executable path when lookup
from the original path fails.

## Testing

Add a Windows regression test that creates a `bin` junction and verifies that
the sandbox setup helper is resolved from the package resources directory.

GitOrigin-RevId: f2f20ce1ccfa95ae65171a03a986d10e2560e696
This commit is contained in:
zm-oai
2026-08-20 01:37:58 +00:00
committed by copyberry
parent 8aaf839774
commit ab82cddd04

View File

@@ -192,23 +192,28 @@ fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
}
pub(crate) fn bundled_executable_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
let dir = exe.parent()?;
let direct_candidate = dir.join(file_name);
if direct_candidate.is_file() {
return Some(direct_candidate);
}
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
&& let Some(package_dir) = dir.parent()
{
let package_resource_candidate = package_dir.join(RESOURCES_DIRNAME).join(file_name);
if package_resource_candidate.is_file() {
return Some(package_resource_candidate);
let find = |exe: &Path| {
let dir = exe.parent()?;
let direct_candidate = dir.join(file_name);
if direct_candidate.is_file() {
return Some(direct_candidate);
}
}
let resource_candidate = dir.join(RESOURCES_DIRNAME).join(file_name);
resource_candidate.is_file().then_some(resource_candidate)
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
&& let Some(package_dir) = dir.parent()
{
let package_resource_candidate = package_dir.join(RESOURCES_DIRNAME).join(file_name);
if package_resource_candidate.is_file() {
return Some(package_resource_candidate);
}
}
let resource_candidate = dir.join(RESOURCES_DIRNAME).join(file_name);
resource_candidate.is_file().then_some(resource_candidate)
};
// Installer bin directories can be junctions, so retry beside the real executable once.
find(exe).or_else(|| find(&dunce::canonicalize(exe).ok()?))
}
fn helper_destination_for_source(
@@ -500,6 +505,38 @@ mod tests {
assert_eq!(resolved, helper);
}
#[test]
fn helper_source_lookup_resolves_bin_junctions() {
let tmp = TempDir::new().expect("tempdir");
let package_dir = tmp.path().join("package");
let bin_dir = package_dir.join(BIN_DIRNAME);
let resources_dir = package_dir.join(RESOURCES_DIRNAME);
let install_dir = tmp.path().join("install");
fs::create_dir_all(&bin_dir).expect("create bin dir");
fs::create_dir_all(&resources_dir).expect("create resources dir");
fs::create_dir_all(&install_dir).expect("create install dir");
fs::write(bin_dir.join("codex.exe"), b"codex").expect("write exe");
let helper = resources_dir.join("codex-windows-sandbox-setup.exe");
fs::write(&helper, b"setup").expect("write helper");
let junction = install_dir.join(BIN_DIRNAME);
let output = std::process::Command::new("cmd")
.args(["/c", "mklink", "/J"])
.arg(&junction)
.arg(&bin_dir)
.output()
.expect("create bin junction");
assert!(output.status.success());
assert_eq!(
bundled_executable_path_for_exe(
&junction.join("codex.exe"),
/*file_name*/ "codex-windows-sandbox-setup.exe"
),
Some(dunce::canonicalize(&helper).expect("canonical helper"))
);
}
#[test]
fn helper_source_lookup_prefers_package_resource_dir_over_bin_resource_dir() {
let tmp = TempDir::new().expect("tempdir");