[codex] Add hermetic Wine test support (#27964)

## Why

We want to make it possible for an app-server orchestrator on one OS to
control an exec-server on another host running a different OS. In
practice this kinda already works if you get lucky and the two hosts
have the same path format, but we mangle quite a lot of operations if
either end is Windows.

We should be able to test the cross-platform interactions for
exec-server, but we want to do this fairly soon and need a lightweight
option for testing. Using Wine to run the Windows side is far from
perfect, but it should give us a decent measure of how well we're
handling the basics of paths, process spawning, shell interaction, etc.

Future changes will add actual exec-server tests and possibly extensions
to the Wine testing environment.

## What

To make the cross-target-triple build easy, these tests are added only
to the Bazel build. This change adds an x86_64 Wine prebuilt managed by
Bazel and some build rules that can set up the needed toolchain
transition.

The support library for running Wine in a test environment created by
the Bazel rules comes with its own basic unit and integration tests.
Their primary priority is to make sure we don't leak child processes on
developer machines and that we can build and launch a basic hello world
binary.

## Validation

Confirmed these new tests are running on the [x86_64 bazel ubuntu
jobs](https://github.com/openai/codex/actions/runs/27446432302/job/81132356855?pr=27937):

```
//bazel/rules/testing/wine:wine-smoke-test                      (cached) PASSED in 3.7s
//bazel/rules/testing/wine:wine-test-support-unit-tests         (cached) PASSED in 15.8s
```
This commit is contained in:
Adam Perry @ OpenAI
2026-06-12 18:24:49 -07:00
committed by GitHub
Unverified
parent aef40ad91f
commit 5c8136f48a
12 changed files with 795 additions and 4 deletions
+2
View File
@@ -326,6 +326,8 @@ http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "ht
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
new_local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository")
include("//bazel/modules:wine.MODULE.bazel")
new_local_repository(
name = "v8_targets",
build_file = "//third_party/v8:BUILD.bazel",
+1
View File
@@ -0,0 +1 @@
exports_files(["wine.MODULE.bazel"])
+13
View File
@@ -0,0 +1,13 @@
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Pin a new-WoW64 build so tests need neither system Wine nor 32-bit host
# libraries.
http_archive(
name = "wine_linux_x86_64",
build_file = "//third_party/wine:BUILD.bazel",
sha256 = "39574efa1132c3ca0d5c77dd2eddbe4a49cca0d6cc2c290ff4924493a1c40314",
strip_prefix = "wine-11.0-amd64-wow64",
urls = [
"https://github.com/Kron4ek/Wine-Builds/releases/download/11.0/wine-11.0-amd64-wow64.tar.xz",
],
)
+6
View File
@@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])
exports_files([
"foreign_platform_binary.bzl",
"wine.bzl",
])
@@ -0,0 +1,53 @@
"""Makes a binary built for a foreign platform available as test data."""
_EXTRA_RUSTC_FLAGS = "@rules_rust//rust/settings:extra_rustc_flags"
def _foreign_platform_transition_impl(settings, attr):
# A transition cannot rewrite a dependency's rule attributes. Use the
# rules_rust build setting when every Rust target in the foreign
# configuration needs additional compiler or linker flags.
return {
"//command_line_option:platforms": [attr.platform],
_EXTRA_RUSTC_FLAGS: settings[_EXTRA_RUSTC_FLAGS] + attr.extra_rustc_flags,
}
_foreign_platform_transition = transition(
implementation = _foreign_platform_transition_impl,
inputs = [_EXTRA_RUSTC_FLAGS],
outputs = [
"//command_line_option:platforms",
_EXTRA_RUSTC_FLAGS,
],
)
def _foreign_platform_binary_impl(ctx):
if len(ctx.attr.binary) != 1:
fail("expected exactly one transitioned binary")
binary = ctx.attr.binary[0][DefaultInfo]
runfiles = ctx.runfiles(transitive_files = binary.files)
runfiles = runfiles.merge(binary.default_runfiles)
return [
DefaultInfo(
files = binary.files,
runfiles = runfiles,
),
]
foreign_platform_binary = rule(
implementation = _foreign_platform_binary_impl,
attrs = {
"binary": attr.label(
cfg = _foreign_platform_transition,
executable = True,
mandatory = True,
),
"extra_rustc_flags": attr.string_list(
doc = "Additional flags applied to every Rust target in the foreign configuration.",
),
"platform": attr.string(mandatory = True),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
doc = "Builds `binary` for `platform` and exposes its files and runfiles.",
)
+76
View File
@@ -0,0 +1,76 @@
"""Macros for cross-building Windows Rust binaries and testing them with Wine."""
load("@rules_rust//rust:defs.bzl", "rust_test")
load("//:defs.bzl", "WINDOWS_GNULLVM_RUSTC_LINK_FLAGS")
load(":foreign_platform_binary.bzl", "foreign_platform_binary")
_WINE_RUNTIME_BINARIES = {
"wine": "@wine_linux_x86_64//:wine",
"wine-runtime-marker": "@wine_linux_x86_64//:runtime_marker",
"wineserver": "@wine_linux_x86_64//:wineserver",
}
def wine_rust_test(
name,
windows_binaries,
data = [],
target_compatible_with = [],
**kwargs):
"""Defines an x86-64 Linux Rust test with a pinned Wine runtime.
Each `windows_binaries` executable is transitioned to GNU/LLVM Windows;
every Rust dependency receives the repository's Windows linker flags while
the test stays on x86-64 Linux. Its environment-variable contract is:
* Each entry contributes `CARGO_BIN_EXE_<binary_name>` for its executable.
* `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`.
These are Bazel runfile locations. Resolve binaries with
`codex_utils_cargo_bin::cargo_bin`; `:wine_test_support` resolves the fixed
runtime names and starts each process in an isolated prefix.
Args:
name: Name of the generated Linux `rust_test`.
windows_binaries: Map from `CARGO_BIN_EXE_*` suffixes to Windows targets.
data: Additional runtime data for the Linux test.
target_compatible_with: Additional compatibility constraints.
**kwargs: Remaining attributes forwarded to `rust_test`.
"""
binaries = dict(_WINE_RUNTIME_BINARIES)
for index, binary_name in enumerate(sorted(windows_binaries.keys())):
if binary_name in binaries:
fail("Windows test binary name collides with Wine runtime: {}".format(binary_name))
transitioned_binary = name + "-windows-binary-" + str(index)
foreign_platform_binary(
name = transitioned_binary,
binary = windows_binaries[binary_name],
extra_rustc_flags = WINDOWS_GNULLVM_RUSTC_LINK_FLAGS,
platform = "//:windows_x86_64_gnullvm",
tags = ["manual"],
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
testonly = True,
visibility = ["//visibility:private"],
)
binaries[binary_name] = ":" + transitioned_binary
rust_test(
name = name,
data = data + [
"@wine_linux_x86_64//:runtime",
] + [binary for binary in binaries.values()],
env = {
"CARGO_BIN_EXE_{}".format(binary_name): "$(rlocationpath {})".format(binary)
for binary_name, binary in binaries.items()
},
target_compatible_with = target_compatible_with + [
"@llvm//constraints/libc:gnu.2.28",
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
**kwargs
)
+48
View File
@@ -0,0 +1,48 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
load("//bazel/rules/testing:wine.bzl", "wine_rust_test")
package(default_visibility = ["//visibility:public"])
rust_library(
name = "wine_test_support",
testonly = True,
srcs = [
"src/lib.rs",
"src/lib_tests.rs",
],
crate_name = "wine_test_support",
crate_root = "src/lib.rs",
edition = "2024",
target_compatible_with = ["@platforms//os:linux"],
deps = [
"//codex-rs/utils/cargo-bin",
"@crates//:anyhow",
"@crates//:tempfile",
"@crates//:tokio",
],
)
rust_binary(
name = "windows-smoke",
testonly = True,
srcs = ["fixtures/windows_smoke.rs"],
crate_name = "wine_smoke",
crate_root = "fixtures/windows_smoke.rs",
edition = "2024",
tags = ["manual"],
target_compatible_with = ["@platforms//os:windows"],
visibility = ["//visibility:private"],
)
wine_rust_test(
name = "wine-test-support-unit-tests",
timeout = "short",
crate = ":wine_test_support",
windows_binaries = {
"wine-smoke": ":windows-smoke",
},
deps = [
"@crates//:futures",
"@crates//:pretty_assertions",
],
)
@@ -0,0 +1,15 @@
use std::io::Write;
fn main() {
println!("WINE_TEST_READY");
std::io::stdout().flush().expect("flush readiness marker");
if std::env::args().any(|arg| arg == "--fail") {
std::process::exit(9);
}
if std::env::args().any(|arg| arg == "--wait") {
loop {
std::thread::park();
}
}
}
+303
View File
@@ -0,0 +1,303 @@
#[cfg(not(target_os = "linux"))]
compile_error!("wine_test_support can only run on Linux");
use std::ffi::OsString;
use std::future::Future;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command as StdCommand;
use std::process::Stdio;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use tempfile::TempDir;
use tokio::process::Child;
use tokio::process::ChildStdout;
use tokio::process::Command as TokioCommand;
/// Builds a command that runs a Windows executable in an isolated Wine prefix.
pub struct WineTestCommand {
executable: PathBuf,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
}
/// Owns a Wine process and its isolated wineserver.
///
/// Call [`Self::scope`] or [`Self::shutdown`] on every successful path. A
/// normal unguarded drop panics, while a drop during unwinding performs
/// blocking cleanup without introducing a second panic.
pub struct WineTestProcess {
processes: Option<WineProcesses>,
}
struct WineProcesses {
child: Child,
cleanup_complete: bool,
prefix: TempDir,
runtime: WineRuntimePaths,
}
struct WineRuntimePaths {
dll_path: PathBuf,
wine: PathBuf,
wineserver: PathBuf,
}
impl WineTestCommand {
/// Creates a Wine command for `executable`.
pub fn new(executable: impl Into<PathBuf>) -> Self {
Self {
executable: executable.into(),
args: Vec::new(),
env: Vec::new(),
}
}
/// Adds an argument passed to the Windows executable.
#[must_use]
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
/// Adds or overrides an environment variable for the Wine process.
#[must_use]
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.env.push((key.into(), value.into()));
self
}
/// Starts the Windows executable with a fresh `WINEPREFIX`.
pub fn spawn(self) -> Result<WineTestProcess> {
let runtime = WineRuntimePaths::from_runfiles()?;
let prefix = TempDir::new().context("create isolated Wine prefix")?;
let mut command = StdCommand::new(&runtime.wine);
configure_wine_environment(&mut command, &runtime, prefix.path());
command
.arg(self.executable)
.args(self.args)
.envs(self.env)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
let mut command = TokioCommand::from(command);
command.kill_on_drop(true);
let child = command
.spawn()
.context("start Windows process under Wine")?;
Ok(WineTestProcess {
processes: Some(WineProcesses {
child,
cleanup_complete: false,
prefix,
runtime,
}),
})
}
}
impl WineTestProcess {
/// Takes the piped standard output of the Wine process.
///
/// This may only be called once for a process created by
/// [`WineTestCommand::spawn`].
pub fn take_stdout(&mut self) -> ChildStdout {
let Some(processes) = self.processes.as_mut() else {
panic!("Wine process guard is missing");
};
let Some(stdout) = processes.child.stdout.take() else {
panic!("Wine process stdout has already been taken");
};
stdout
}
/// Runs `future`, then asynchronously tears down Wine before returning.
///
/// If both the scoped operation and teardown fail, the operation error is
/// returned with the teardown error attached as context. A panic in the
/// scoped operation triggers the blocking unwind-time fallback instead.
pub async fn scope<T>(self, future: impl Future<Output = Result<T>>) -> Result<T> {
let scope_result = future.await;
let shutdown_result = self.shutdown().await;
match (scope_result, shutdown_result) {
(Ok(value), Ok(())) => Ok(value),
(Err(error), Ok(())) => Err(error),
(Ok(_), Err(error)) => Err(error),
(Err(error), Err(shutdown_error)) => {
Err(error.context(format!("Wine teardown also failed: {shutdown_error:#}")))
}
}
}
/// Kills the Windows process, waits for it, and stops its wineserver.
pub async fn shutdown(mut self) -> Result<()> {
let Some(processes) = self.processes.as_mut() else {
anyhow::bail!("Wine process guard is missing");
};
let result = processes.shutdown().await;
self.processes.take();
result
}
}
impl Drop for WineTestProcess {
fn drop(&mut self) {
// Panicking here starts unwinding, after which WineProcesses performs
// the blocking fallback while its field is dropped.
if self.processes.is_some() && !std::thread::panicking() {
panic!("WineTestProcess dropped without async teardown");
}
}
}
impl WineRuntimePaths {
fn from_runfiles() -> Result<Self> {
let wine = codex_utils_cargo_bin::cargo_bin("wine")?;
let runtime_marker = codex_utils_cargo_bin::cargo_bin("wine-runtime-marker")?;
let dll_path = runtime_marker
.parent()
.context("locate Wine runtime directory")?
.to_path_buf();
let wineserver = codex_utils_cargo_bin::cargo_bin("wineserver")?;
Ok(Self {
dll_path,
wine,
wineserver,
})
}
}
impl WineProcesses {
async fn shutdown(&mut self) -> Result<()> {
let (kill_result, check_exit_status) = match self.child.try_wait() {
Ok(Some(_)) => (Ok(()), true),
Ok(None) => (
self.child
.start_kill()
.context("kill Windows process running under Wine"),
false,
),
Err(error) => (Err(error).context("check Windows process status"), false),
};
let wait_result = self
.child
.wait()
.await
.context("wait for Windows process running under Wine")
.and_then(|status| {
anyhow::ensure!(
!check_exit_status || status.success(),
"Windows process exited with {status}"
);
Ok(())
});
let wineserver_result = async {
let mut command = TokioCommand::from(self.stop_wineserver_command());
let status = command.status().await.context("stop isolated wineserver")?;
anyhow::ensure!(status.success(), "wineserver exited with {status}");
Ok(())
}
.await;
// Every cleanup action has been attempted, so an individual error
// should not cause the blocking fallback to repeat them.
self.cleanup_complete = true;
kill_result?;
wait_result?;
wineserver_result
}
fn stop_wineserver_command(&self) -> StdCommand {
let mut command = StdCommand::new(&self.runtime.wineserver);
configure_wine_environment(&mut command, &self.runtime, self.prefix.path());
command
.args(["-k", "-w"])
.stdout(Stdio::null())
.stderr(Stdio::null());
command
}
fn shutdown_blocking(&mut self) {
log_panic_cleanup(format_args!(
"Wine panic cleanup starting for prefix {}",
self.prefix.path().display()
));
if let Err(error) = self.child.start_kill() {
log_panic_cleanup(format_args!(
"Wine panic cleanup could not kill its child: {error}"
));
}
log_panic_cleanup(format_args!("Wine panic cleanup waiting for its child"));
loop {
match self.child.try_wait() {
Ok(Some(status)) => {
log_panic_cleanup(format_args!(
"Wine panic cleanup child exited with {status}"
));
break;
}
Ok(None) => std::thread::sleep(Duration::from_millis(10)),
Err(error) => {
log_panic_cleanup(format_args!(
"Wine panic cleanup could not wait for its child: {error}"
));
break;
}
}
}
log_panic_cleanup(format_args!("Wine panic cleanup stopping its wineserver"));
match self.stop_wineserver_command().status() {
Ok(status) => log_panic_cleanup(format_args!(
"Wine panic cleanup wineserver exited with {status}"
)),
Err(error) => log_panic_cleanup(format_args!(
"Wine panic cleanup could not stop its wineserver: {error}"
)),
}
self.cleanup_complete = true;
log_panic_cleanup(format_args!("Wine panic cleanup complete"));
}
}
impl Drop for WineProcesses {
fn drop(&mut self) {
// Never introduce a second panic while unwinding. Blocking here is
// intentional because test failures must not leak Wine children.
if !self.cleanup_complete && std::thread::panicking() {
self.shutdown_blocking();
}
}
}
fn log_panic_cleanup(args: std::fmt::Arguments<'_>) {
let _ = writeln!(std::io::stderr().lock(), "{args}");
}
fn configure_wine_environment(command: &mut StdCommand, runtime: &WineRuntimePaths, prefix: &Path) {
command
.env_remove("DISPLAY")
.env("HOME", prefix)
.env("XDG_RUNTIME_DIR", prefix)
.env("WINEARCH", "win64")
.env("WINEPREFIX", prefix)
.env("WINEDLLPATH", &runtime.dll_path)
.env("WINESERVER", &runtime.wineserver)
.env("WINEDEBUG", "-all")
.env("WINEDLLOVERRIDES", "mscoree,mshtml,winegstreamer=")
.env("LANG", "C.UTF-8")
.env("LC_ALL", "C.UTF-8")
.env("LC_CTYPE", "C.UTF-8")
.env("TEMP", r"C:\windows\temp")
.env("TMP", r"C:\windows\temp");
}
#[cfg(test)]
#[path = "lib_tests.rs"]
mod tests;
+232
View File
@@ -0,0 +1,232 @@
use std::any::Any;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use futures::FutureExt;
use pretty_assertions::assert_eq;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio::process::Command as TokioCommand;
use super::WineTestCommand;
use super::WineTestProcess;
async fn waiting_smoke_process() -> Result<WineTestProcess> {
let executable = codex_utils_cargo_bin::cargo_bin("wine-smoke")?;
let mut process = WineTestCommand::new(executable).arg("--wait").spawn()?;
let mut lines = BufReader::new(process.take_stdout()).lines();
let ready_line = lines
.next_line()
.await?
.context("Windows smoke process exited before becoming ready")?;
assert_eq!(ready_line, "WINE_TEST_READY");
Ok(process)
}
fn prefix_path(process: &WineTestProcess) -> PathBuf {
process
.processes
.as_ref()
.expect("Wine process guard")
.prefix
.path()
.to_path_buf()
}
fn assert_prefix_removed(prefix: &Path) {
assert!(
!prefix.exists(),
"Wine prefix remains: {}",
prefix.display()
);
}
fn assert_panic_message(panic: Box<dyn Any + Send>, expected: &str) {
assert_eq!(panic.downcast_ref::<&str>(), Some(&expected));
}
async fn assert_future_panics<T>(future: impl Future<Output = T>, expected: &str) {
let panic = match AssertUnwindSafe(future).catch_unwind().await {
Ok(_) => panic!("future should panic"),
Err(panic) => panic,
};
assert_panic_message(panic, expected);
}
async fn process_with_failing_wineserver_stop() -> Result<WineTestProcess> {
let mut process = waiting_smoke_process().await?;
let processes = process.processes.as_mut().expect("Wine process guard");
let mut command = TokioCommand::from(processes.stop_wineserver_command());
let status = command
.status()
.await
.context("pre-stop isolated wineserver")?;
assert!(status.success(), "wineserver exited with {status}");
processes.runtime.wineserver = processes.prefix.path().join("missing-wineserver");
Ok(process)
}
#[tokio::test]
async fn dropping_without_teardown_panics() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
assert_future_panics(
async move { drop(process) },
"WineTestProcess dropped without async teardown",
)
.await;
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn dropping_while_panicking_does_not_panic_again() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
assert_future_panics(
async move {
let _process = process;
panic!("sentinel panic");
},
"sentinel panic",
)
.await;
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn async_teardown_disarms_drop_bomb() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
process.shutdown().await?;
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn take_stdout_panics_when_called_twice() -> Result<()> {
let mut process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
assert_future_panics(
async {
process.take_stdout();
},
"Wine process stdout has already been taken",
)
.await;
process.shutdown().await?;
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn scope_returns_value_and_tears_down() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
let value = process
.scope(async { Ok::<_, anyhow::Error>("scope value") })
.await?;
assert_eq!(value, "scope value");
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn scope_returns_body_error_and_tears_down() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
let error = process
.scope(async { Err::<(), _>(anyhow!("scope body failed")) })
.await
.expect_err("scope body should fail");
assert_eq!(error.to_string(), "scope body failed");
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn scope_panic_preserves_panic_and_tears_down() -> Result<()> {
let process = waiting_smoke_process().await?;
let prefix = prefix_path(&process);
assert_future_panics(
process.scope::<()>(async { panic!("scope panic") }),
"scope panic",
)
.await;
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn shutdown_reports_nonzero_process_exit() -> Result<()> {
let executable = codex_utils_cargo_bin::cargo_bin("wine-smoke")?;
let mut process = WineTestCommand::new(executable).arg("--fail").spawn()?;
let prefix = prefix_path(&process);
let status = process
.processes
.as_mut()
.expect("Wine process guard")
.child
.wait()
.await?;
assert!(
!status.success(),
"Windows smoke process unexpectedly passed"
);
let error = process.shutdown().await.expect_err("shutdown should fail");
assert!(error.to_string().starts_with("Windows process exited with"));
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn scope_preserves_body_error_when_teardown_also_fails() -> Result<()> {
let process = process_with_failing_wineserver_stop().await?;
let prefix = prefix_path(&process);
let error = process
.scope(async { Err::<(), _>(anyhow!("scope body failed")) })
.await
.expect_err("scope body and teardown should fail");
assert!(
error
.to_string()
.starts_with("Wine teardown also failed: stop isolated wineserver"),
"unexpected error: {error:#}"
);
assert_eq!(
error.chain().last().map(ToString::to_string),
Some("scope body failed".to_string())
);
assert_prefix_removed(&prefix);
Ok(())
}
#[tokio::test]
async fn shutdown_returns_teardown_error() -> Result<()> {
let process = process_with_failing_wineserver_stop().await?;
let prefix = prefix_path(&process);
let error = process
.shutdown()
.await
.expect_err("shutdown should report a wineserver failure");
assert_eq!(error.to_string(), "stop isolated wineserver");
assert_prefix_removed(&prefix);
Ok(())
}
+5 -3
View File
@@ -5,11 +5,13 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_mac
# Match Cargo's Windows linker behavior so Bazel-built binaries and tests use
# the same stack reserve on both Windows ABIs and resolve UCRT imports on MSVC.
WINDOWS_RUSTC_LINK_FLAGS = select({
"@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm": [
WINDOWS_GNULLVM_RUSTC_LINK_FLAGS = [
"-C",
"link-arg=-Wl,--stack,8388608", # 8 MiB
],
]
WINDOWS_RUSTC_LINK_FLAGS = select({
"@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm": WINDOWS_GNULLVM_RUSTC_LINK_FLAGS,
"@rules_rs//rs/experimental/platforms/constraints:windows_msvc": [
"-C",
"link-arg=/STACK:8388608", # 8 MiB
+40
View File
@@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
exports_files([
"bin/wine",
"bin/wineserver",
])
filegroup(
name = "wine",
srcs = ["bin/wine"],
tags = ["manual"],
)
filegroup(
name = "wineserver",
srcs = ["bin/wineserver"],
tags = ["manual"],
)
filegroup(
name = "runtime_marker",
srcs = ["lib/wine/x86_64-unix/ntdll.so"],
tags = ["manual"],
)
# Static import libraries are build-time-only, so omit them from test runfiles.
filegroup(
name = "runtime",
srcs = glob(
[
"lib/wine/**",
"share/wine/**",
],
# This BUILD file is also loaded from the source tree, where the
# archive-only paths are intentionally absent.
allow_empty = True,
exclude = ["**/*.a"],
),
tags = ["manual"],
)