#[cfg(feature = "native")]
use std::{
env,
fs::File,
io::{self, Read, Write},
path::{Path, PathBuf},
};
#[allow(clippy::unnecessary_wraps)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=proto/content.proto");
println!("cargo:rerun-if-changed=proto/localharness.proto");
println!("cargo:rerun-if-env-changed=ANTIGRAVITY_HARNESS_PATH");
#[cfg(feature = "native")]
{
compile_protos()?;
resolve_or_download_binary()?;
}
Ok(())
}
#[cfg(feature = "native")]
fn compile_protos() -> Result<(), Box<dyn std::error::Error>> {
if env::var("PROTOC").is_err()
&& let Ok(path) = protoc_bin_vendored::protoc_bin_path()
{
unsafe {
env::set_var("PROTOC", path);
}
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
let proto_dir = manifest_dir.join("proto");
let content_proto = proto_dir.join("content.proto");
let localharness_proto = proto_dir.join("localharness.proto");
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let descriptor_path = out_dir.join("proto_descriptor.bin");
let mut prost_config = prost_build::Config::new();
prost_config
.file_descriptor_set_path(&descriptor_path)
.compile_well_known_types()
.extern_path(".google.protobuf", "::pbjson_types");
prost_config.compile_protos(&[localharness_proto, content_proto], &[proto_dir])?;
let descriptor_set = std::fs::read(descriptor_path)?;
pbjson_build::Builder::new()
.register_descriptors(&descriptor_set)?
.build(&[".antigravity.localharness", ".genai"])?;
Ok(())
}
#[cfg(feature = "native")]
fn resolve_or_download_binary() -> Result<(), Box<dyn std::error::Error>> {
const ENV_HARNESS_PATH: &str = "ANTIGRAVITY_HARNESS_PATH";
const ENV_HOME: &str = "HOME";
const GEMINI_CACHE_DIR: &str = ".gemini";
const ANTIGRAVITY_DIR: &str = "antigravity";
const BIN_DIR: &str = "bin";
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let is_windows = env::var("CARGO_CFG_TARGET_OS").is_ok_and(|os| os == "windows");
let bin_name = if is_windows {
"localharness.exe"
} else {
"localharness"
};
let target_bin_path = out_dir.join(bin_name);
if target_bin_path.is_file() {
println!(
"cargo:rustc-env=NATIVE_HARNESS_PATH={}",
target_bin_path.display()
);
return Ok(());
}
if let Ok(custom_path) = env::var(ENV_HARNESS_PATH) {
let p = PathBuf::from(custom_path);
if p.is_file() {
println!("cargo:rustc-env=NATIVE_HARNESS_PATH={}", p.display());
return Ok(());
}
}
if let Some(home) = env::var_os(ENV_HOME).map(PathBuf::from) {
let candidate = home
.join(GEMINI_CACHE_DIR)
.join(ANTIGRAVITY_DIR)
.join(BIN_DIR)
.join(bin_name);
if candidate.is_file() {
if let Err(e) = std::fs::copy(&candidate, &target_bin_path) {
eprintln!("Failed to copy binary from {}: {}", candidate.display(), e);
} else {
#[cfg(unix)]
set_executable_permission(&target_bin_path)?;
println!(
"cargo:rustc-env=NATIVE_HARNESS_PATH={}",
target_bin_path.display()
);
return Ok(());
}
}
}
let target_os = env::var("CARGO_CFG_TARGET_OS")?;
let target_arch = env::var("CARGO_CFG_TARGET_ARCH")?;
println!(
"cargo:warning=Downloading local proxy binary from PyPI for {target_os}-{target_arch}..."
);
download_and_extract_wheel(&target_os, &target_arch, &target_bin_path)?;
println!(
"cargo:rustc-env=NATIVE_HARNESS_PATH={}",
target_bin_path.display()
);
Ok(())
}
#[cfg(feature = "native")]
fn download_and_extract_wheel(
target_os: &str,
target_arch: &str,
dest_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let platform_tag = match (target_os, target_arch) {
("linux", "x86_64") => "manylinux_2_17_x86_64",
("linux", "aarch64") => "manylinux_2_17_aarch64",
("macos", "aarch64") => "macosx_11_0_arm64",
("macos", "x86_64") => "macosx_11_0_x86_64",
("windows", "x86_64") => "win_amd64",
("windows", "aarch64") => "win_arm64",
(os, arch) => {
return Err(format!("Unsupported target platform: {os}-{arch}").into());
}
};
let pypi_url = "https://pypi.org/pypi/google-antigravity/json";
let pypi_resp = ureq::get(pypi_url).call()?;
let json_body: serde_json::Value = pypi_resp.into_body().read_json()?;
let releases = json_body
.get("releases")
.and_then(|r| r.as_object())
.ok_or("Invalid PyPI response: missing releases")?;
let version = json_body
.get("info")
.and_then(|i| i.get("version"))
.and_then(|v| v.as_str())
.ok_or("Invalid PyPI response: missing version")?;
let files = releases
.get(version)
.and_then(|f| f.as_array())
.ok_or_else(|| format!("No files found for version {version}"))?;
let wheel_file = files
.iter()
.find(|f| {
let filename = f.get("filename").and_then(|n| n.as_str()).unwrap_or("");
Path::new(filename)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
&& filename.contains(platform_tag)
})
.ok_or_else(|| {
format!("No wheel found matching platform tag '{platform_tag}' for version {version}")
})?;
let download_url = wheel_file
.get("url")
.and_then(|u| u.as_str())
.ok_or("Missing download URL for wheel")?;
let wheel_resp = ureq::get(download_url).call()?;
let mut wheel_bytes = Vec::new();
wheel_resp
.into_body()
.into_reader()
.read_to_end(&mut wheel_bytes)?;
let reader = io::Cursor::new(wheel_bytes);
let mut zip_archive = zip::ZipArchive::new(reader)?;
let mut found = false;
for i in 0..zip_archive.len() {
let mut file = zip_archive.by_index(i)?;
let name = file.name().to_string();
if name.ends_with("google/antigravity/bin/localharness")
|| name.ends_with("google/antigravity/bin/localharness.exe")
{
if let Some(parent) = dest_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut out = File::create(dest_path)?;
io::copy(&mut file, &mut out)?;
out.flush()?;
found = true;
break;
}
}
if !found {
return Err("Could not find localharness binary inside downloaded wheel".into());
}
#[cfg(unix)]
set_executable_permission(dest_path)?;
Ok(())
}
#[cfg(all(feature = "native", unix))]
fn set_executable_permission(path: &Path) -> Result<(), io::Error> {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(path, perms)?;
Ok(())
}