Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bazel/modules/wine.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,16 @@ http_archive(
"https://github.com/Kron4ek/Wine-Builds/releases/download/11.0/wine-11.0-amd64-wow64.tar.xz",
],
)

# Pin the self-contained Windows distribution so Wine tests need neither a
# system PowerShell installation nor a separate .NET runtime. This intentionally
# stays on 7.2.24 for the test fixture: 7.4.16 and 7.6.2 currently fail during
# CLR startup under the pinned Wine 11 runtime, while 7.2.24 runs successfully.
http_archive(
name = "powershell_windows_x86_64",
build_file = "//third_party/powershell:BUILD.bazel",
sha256 = "a1ccb6d8ad52f917470a136c3752af4465f261bcbe570cf44f52aa69ae6e867e",
urls = [
"https://github.com/PowerShell/PowerShell/releases/download/v7.2.24/PowerShell-7.2.24-win-x64.zip",
],
)
16 changes: 12 additions & 4 deletions bazel/rules/testing/wine.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ load("//:defs.bzl", "WINDOWS_GNULLVM_RUSTC_LINK_FLAGS")
load(":foreign_platform_binary.bzl", "foreign_platform_binary")

_WINE_RUNTIME_BINARIES = {
"pwsh": "@powershell_windows_x86_64//:pwsh",
"pwsh-runtime-marker": "@powershell_windows_x86_64//:runtime_marker",
"wine": "@wine_linux_x86_64//:wine",
"wine-runtime-marker": "@wine_linux_x86_64//:runtime_marker",
"wineserver": "@wine_linux_x86_64//:wineserver",
Expand All @@ -28,6 +30,9 @@ def wine_rust_test(
* `CARGO_BIN_EXE_wine` and `CARGO_BIN_EXE_wineserver` identify Wine tools.
* `CARGO_BIN_EXE_wine-runtime-marker` identifies a file whose parent is the
Wine DLL directory to use as `WINEDLLPATH`.
* `CARGO_BIN_EXE_pwsh` identifies the pinned PowerShell executable and
`CARGO_BIN_EXE_pwsh-runtime-marker` identifies a file whose parent is the
complete PowerShell runtime.

These are Bazel runfile locations. Resolve binaries with
`codex_utils_cargo_bin::cargo_bin`; `:wine_test_support` resolves the fixed
Expand All @@ -42,9 +47,14 @@ def wine_rust_test(
**kwargs: Remaining attributes forwarded to `rust_test`.
"""
binaries = dict(_WINE_RUNTIME_BINARIES)
runtime_data = [
"@powershell_windows_x86_64//:runtime",
"@wine_linux_x86_64//:runtime",
]

for binary_name in sorted(host_binaries.keys()):
if binary_name in binaries:
fail("host test binary name collides with Wine runtime: {}".format(binary_name))
fail("host test binary name collides with test runtime: {}".format(binary_name))
binaries[binary_name] = host_binaries[binary_name]

for index, binary_name in enumerate(sorted(windows_binaries.keys())):
Expand All @@ -68,9 +78,7 @@ def wine_rust_test(

rust_test(
name = name,
data = data + [
"@wine_linux_x86_64//:runtime",
] + [binary for binary in binaries.values()],
data = data + runtime_data + [binary for binary in binaries.values()],
env = {
"CARGO_BIN_EXE_{}".format(binary_name): "$(rlocationpath {})".format(binary)
for binary_name, binary in binaries.items()
Expand Down
1 change: 1 addition & 0 deletions bazel/rules/testing/wine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rust_library(
target_compatible_with = ["@platforms//os:linux"],
deps = [
"//codex-rs/utils/cargo-bin",
"//codex-rs/utils/pty",
"@crates//:anyhow",
"@crates//:tempfile",
"@crates//:tokio",
Expand Down
79 changes: 79 additions & 0 deletions bazel/rules/testing/wine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
compile_error!("wine_test_support can only run on Linux");

use std::ffi::OsString;
use std::fs;
use std::future::Future;
use std::io::Write;
use std::path::Path;
Expand Down Expand Up @@ -42,6 +43,7 @@ struct WineProcesses {

struct WineRuntimePaths {
dll_path: PathBuf,
powershell_runtime: PathBuf,
wine: PathBuf,
wineserver: PathBuf,
}
Expand Down Expand Up @@ -74,6 +76,7 @@ impl WineTestCommand {
pub fn spawn(self) -> Result<WineTestProcess> {
let runtime = WineRuntimePaths::from_runfiles()?;
let prefix = TempDir::new().context("create isolated Wine prefix")?;
install_powershell_runtime(prefix.path(), &runtime.powershell_runtime)?;
let mut command = StdCommand::new(&runtime.wine);
configure_wine_environment(&mut command, &runtime, prefix.path());
command
Expand Down Expand Up @@ -164,8 +167,13 @@ impl WineRuntimePaths {
.context("locate Wine runtime directory")?
.to_path_buf();
let wineserver = codex_utils_cargo_bin::cargo_bin("wineserver")?;
let powershell_runtime = codex_utils_cargo_bin::cargo_bin("pwsh-runtime-marker")?
.parent()
.context("locate PowerShell runtime directory")?
.to_path_buf();
Ok(Self {
dll_path,
powershell_runtime,
wine,
wineserver,
})
Expand Down Expand Up @@ -298,6 +306,77 @@ fn configure_wine_environment(command: &mut StdCommand, runtime: &WineRuntimePat
.env("TMP", r"C:\windows\temp");
}

/// Installs the complete pinned PowerShell distribution where Windows tooling
/// expects to discover PowerShell 7.
///
/// `pwsh.exe` is not a standalone executable: it loads its adjacent .NET host,
/// managed assemblies, native libraries, modules, and configuration files at
/// startup. The Bazel archive is exposed through runfiles rather than a normal
/// Windows installation, while shell detection deliberately probes the
/// conventional `C:\Program Files\PowerShell\7` fallback. We therefore have to
/// reproduce the archive's directory tree inside each isolated Wine prefix;
/// copying only the executable would fail before a command could run.
fn install_powershell_runtime(prefix: &Path, runtime: &Path) -> Result<()> {
Comment thread
anp-oai marked this conversation as resolved.
let powershell_parent = prefix
.join("drive_c")
.join("Program Files")
.join("PowerShell");
fs::create_dir_all(&powershell_parent).context("create PowerShell installation parent")?;
let destination = powershell_parent.join("7");
materialize_runtime_directory(runtime, &destination)
}

/// Recursively reproduces a runfiles directory in a writable Wine prefix.
///
/// Bazel runfiles may be immutable and may contain the PowerShell distribution
/// on a different filesystem from the temporary prefix. Hard links avoid
/// repeatedly copying the roughly hundred-megabyte runtime when both locations
/// share a filesystem; the copy fallback preserves correctness for sandbox or
/// remote-execution layouts where cross-device hard links are unavailable.
fn materialize_runtime_directory(source: &Path, destination: &Path) -> Result<()> {
fs::create_dir_all(destination).with_context(|| {
format!(
"create PowerShell runtime directory {}",
destination.display()
)
})?;
for entry in fs::read_dir(source)
.with_context(|| format!("read PowerShell runtime directory {}", source.display()))?
{
let entry = entry.context("read PowerShell runtime entry")?;
let source_path = entry.path();
let destination_path = destination.join(entry.file_name());
let file_type = entry
.file_type()
.with_context(|| format!("inspect PowerShell runtime entry {}", source_path.display()))?;
Comment thread
anp-oai marked this conversation as resolved.
if file_type.is_dir() {
// PowerShell resolves assemblies and modules by their relative
// locations, so flattening the archive is not an option.
materialize_runtime_directory(&source_path, &destination_path)?;
} else if file_type.is_file() {
// A hard link gives each prefix the expected installation layout
// without duplicating the large runtime in the common local case.
if fs::hard_link(&source_path, &destination_path).is_err() {
// Cross-device links are common under Bazel sandboxing and
// remote execution, where an ordinary copy is still valid.
fs::copy(&source_path, &destination_path).with_context(|| {
format!(
"copy PowerShell runtime file {} to {}",
source_path.display(),
destination_path.display()
)
})?;
}
} else {
anyhow::bail!(
"unsupported PowerShell runtime entry type at {}",
source_path.display()
);
}
}
Ok(())
}

#[cfg(test)]
#[path = "lib_tests.rs"]
mod tests;
Loading
Loading