agent-desktop/crates/macos/build.rs
Lahfir 3f322728b4
feat!: implement Playwright-grade foundation contract
Settle the Playwright-grade reliability contract in agent-desktop-core
before the Windows/Linux adapters are built, so they inherit it instead
of redesigning it. Every command now observes, waits, verifies, and
reports honestly instead of firing blindly.

Highlights: capability-supertrait split of PlatformAdapter with
not_supported() defaults; canonical role/state vocabulary with live
`is --property visible`; display enumeration (`list-displays`) and honest
`--screen` with scale factor; truthful Automation permission; `native_id`
identity spine; window-id-first resolution; serializable `LocatorQuery`
with live `find`; default-on auto-wait before every ref action; three-way
`hit_test` occlusion gate; `scroll_into_view` in core; core accessible-name
precedence; typed `ActionStep` delivery tier; `ProcessState` and
`APP_UNRESPONSIVE`; `LaunchOptions`; baseline-diff desktop signals
(`wait --event`); typed clipboard (`Text`/`Image`/`FileUrls`); mouse
modifier chords and `mouse-wheel`. Hardened through a 35-reviewer pass with
independent validation and a green live e2e gate (109/0), plus a
head-vs-main performance comparison harness.

BREAKING CHANGE: default-on auto-wait changes the timing of every
previously-untouched ref-action call (bounded 5000 ms default; `--timeout-ms 0`
restores single-shot). `ENVELOPE_VERSION` is now `2.1` (adds the
`APP_UNRESPONSIVE` code and process state in error details). FFI ABI major
is `3` (append-only struct evolution; `wait --event` is intentionally not
exposed over FFI). The legacy string clipboard API is removed in favor of
typed content. `key-down`/`key-up` fail closed until daemon-owned held input
exists. `close-app` verifies termination and the osascript fallback path is
removed. `--text` matching is subtree containment: `find --text X --first`
returns the outermost matching container.
2026-07-20 00:21:38 -07:00

134 lines
4.4 KiB
Rust

use std::path::PathBuf;
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=src/system/launch_bridge.m");
println!("cargo:rerun-if-changed=src/system/appkit_bridge.m");
println!("cargo:rerun-if-changed=src/system/screen_bridge.m");
println!("cargo:rerun-if-env-changed=TARGET");
println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
return;
}
let target = required_target();
let deployment = deployment_target();
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown".into());
println!("cargo:rustc-env=AGENT_DESKTOP_MACOS_HELPER_BUILD_ID={version}:{target}:{deployment}");
let Some(out_dir) = std::env::var_os("OUT_DIR").map(PathBuf::from) else {
eprintln!("OUT_DIR is required");
std::process::exit(1);
};
let object = out_dir.join("launch_bridge.o");
let appkit_object = out_dir.join("appkit_bridge.o");
let screen_object = out_dir.join("screen_bridge.o");
let archive = out_dir.join("libagent_desktop_launch_bridge.a");
run(
Command::new("xcrun")
.args(["--sdk", "macosx", "clang"])
.args(["-fobjc-arc", "-fblocks", "-target", &target])
.arg(format!("-mmacosx-version-min={deployment}"))
.args([
"-Wall",
"-Wextra",
"-Werror",
"-c",
"src/system/appkit_bridge.m",
"-o",
])
.arg(&appkit_object),
"compile Objective-C AppKit bridge",
);
run(
Command::new("xcrun")
.args(["--sdk", "macosx", "clang"])
.args(["-fobjc-arc", "-fblocks", "-target", &target])
.arg(format!("-mmacosx-version-min={deployment}"))
.args([
"-Wall",
"-Wextra",
"-Werror",
"-c",
"src/system/launch_bridge.m",
"-o",
])
.arg(&object),
"compile Objective-C launch bridge",
);
run(
Command::new("xcrun")
.args(["--sdk", "macosx", "clang"])
.args(["-fobjc-arc", "-target", &target])
.arg(format!("-mmacosx-version-min={deployment}"))
.args([
"-Wall",
"-Wextra",
"-Werror",
"-c",
"src/system/screen_bridge.m",
"-o",
])
.arg(&screen_object),
"compile Objective-C screen bridge",
);
run(
Command::new("xcrun")
.args(["--sdk", "macosx", "ar", "rcs"])
.arg(&archive)
.arg(&object)
.arg(&appkit_object)
.arg(&screen_object),
"archive Objective-C launch bridge",
);
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=agent_desktop_launch_bridge");
println!("cargo:rustc-link-lib=framework=AppKit");
}
fn required_target() -> String {
match std::env::var("TARGET") {
Ok(target)
if matches!(
target.as_str(),
"aarch64-apple-darwin" | "x86_64-apple-darwin"
) =>
{
target
}
Ok(target) => {
eprintln!("unsupported macOS launch bridge target: {target}");
std::process::exit(1);
}
Err(error) => {
eprintln!("TARGET is required: {error}");
std::process::exit(1);
}
}
}
fn deployment_target() -> String {
let deployment = std::env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| "10.15".into());
let supported = deployment
.split_once('.')
.and_then(|(major, minor)| Some((major.parse::<u32>().ok()?, minor.parse::<u32>().ok()?)))
.is_some_and(|version| version >= (10, 15));
if supported {
deployment
} else {
eprintln!("MACOSX_DEPLOYMENT_TARGET must be 10.15 or newer");
std::process::exit(1);
}
}
fn run(command: &mut Command, label: &str) {
match command.status() {
Ok(status) if status.success() => {}
Ok(status) => {
eprintln!("{label} failed with {status}");
std::process::exit(1);
}
Err(error) => {
eprintln!("{label}: {error}");
std::process::exit(1);
}
}
}