use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
const PLAYWRIGHT_VERSION: &str = "1.58.2";
const DRIVER_BASE_URL: &str = "https://playwright.azureedge.net/builds/driver";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
if std::env::var("DOCS_RS").is_ok() {
println!("cargo:rustc-env=PLAYWRIGHT_DRIVER_DIR=");
println!(
"cargo:rustc-env=PLAYWRIGHT_DRIVER_VERSION={}",
PLAYWRIGHT_VERSION
);
println!("cargo:rustc-env=PLAYWRIGHT_DRIVER_PLATFORM=docs-rs");
return;
}
let drivers_dir = get_drivers_dir();
let platform = detect_platform();
let driver_dir = drivers_dir.join(format!("playwright-{}-{}", PLAYWRIGHT_VERSION, platform));
if driver_dir.exists() {
set_output_env_vars(&driver_dir, platform);
return;
}
println!(
"cargo:warning=Downloading Playwright driver {} for {}...",
PLAYWRIGHT_VERSION, platform
);
match download_and_extract_driver(&drivers_dir, platform) {
Ok(extracted_dir) => {
println!(
"cargo:warning=Playwright driver downloaded to {}",
extracted_dir.display()
);
set_output_env_vars(&extracted_dir, platform);
}
Err(e) => {
println!("cargo:warning=Failed to download Playwright driver: {}", e);
println!("cargo:warning=The driver will need to be installed manually or via npm.");
println!(
"cargo:warning=You can set PLAYWRIGHT_DRIVER_PATH to specify driver location."
);
}
}
}
fn get_drivers_dir() -> PathBuf {
if let Ok(workspace_dir) = env::var("CARGO_WORKSPACE_DIR") {
let drivers_dir = PathBuf::from(workspace_dir).join("drivers");
println!(
"cargo:warning=Using workspace drivers directory: {}",
drivers_dir.display()
);
return drivers_dir;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let mut current = manifest_dir.as_path();
while let Some(parent) = current.parent() {
let cargo_toml = parent.join("Cargo.toml");
if cargo_toml.exists() {
if let Ok(contents) = fs::read_to_string(&cargo_toml) {
if contents.contains("[workspace]") {
let drivers_dir = parent.join("drivers");
println!("cargo:warning=Found workspace at: {}", parent.display());
println!(
"cargo:warning=Using drivers directory: {}",
drivers_dir.display()
);
return drivers_dir;
}
}
}
current = parent;
}
let cache_dir = dirs::cache_dir()
.expect("Could not determine cache directory")
.join("playwright-rust")
.join("drivers");
println!(
"cargo:warning=No workspace found, using cache directory: {}",
cache_dir.display()
);
println!(
"cargo:warning=This matches playwright-python's approach for system-wide driver installation"
);
cache_dir
}
fn detect_platform() -> &'static str {
let os = env::consts::OS;
let arch = env::consts::ARCH;
match (os, arch) {
("macos", "x86_64") => "mac",
("macos", "aarch64") => "mac-arm64",
("linux", "x86_64") => "linux",
("linux", "aarch64") => "linux-arm64",
("windows", "x86_64") => "win32_x64",
("windows", "aarch64") => "win32_arm64",
_ => {
println!("cargo:warning=Unsupported platform: {} {}", os, arch);
println!("cargo:warning=Defaulting to linux platform");
"linux"
}
}
}
fn download_and_extract_driver(drivers_dir: &Path, platform: &str) -> io::Result<PathBuf> {
fs::create_dir_all(drivers_dir)?;
let filename = format!("playwright-{}-{}.zip", PLAYWRIGHT_VERSION, platform);
let url = format!("{}/{}", DRIVER_BASE_URL, filename);
println!("cargo:warning=Downloading from: {}", url);
let response = reqwest::blocking::get(&url)
.map_err(|e| io::Error::other(format!("Download failed: {}", e)))?;
if !response.status().is_success() {
return Err(io::Error::other(format!(
"Download failed with status: {}",
response.status()
)));
}
let bytes = response
.bytes()
.map_err(|e| io::Error::other(format!("Failed to read response: {}", e)))?;
println!("cargo:warning=Downloaded {} bytes", bytes.len());
let cursor = io::Cursor::new(bytes);
let mut archive = zip::ZipArchive::new(cursor)
.map_err(|e| io::Error::other(format!("Failed to open ZIP: {}", e)))?;
let extract_dir = drivers_dir.join(format!("playwright-{}-{}", PLAYWRIGHT_VERSION, platform));
println!("cargo:warning=Extracting to: {}", extract_dir.display());
for i in 0..archive.len() {
let mut file = archive
.by_index(i)
.map_err(|e| io::Error::other(format!("Failed to read ZIP entry: {}", e)))?;
let outpath = extract_dir.join(file.name());
if file.is_dir() {
fs::create_dir_all(&outpath)?;
} else {
if let Some(parent) = outpath.parent() {
fs::create_dir_all(parent)?;
}
let mut outfile = fs::File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if outpath.ends_with("node")
|| outpath.extension().and_then(|s| s.to_str()) == Some("sh")
{
let mut perms = fs::metadata(&outpath)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&outpath, perms)?;
}
}
}
}
println!(
"cargo:warning=Successfully extracted {} files",
archive.len()
);
Ok(extract_dir)
}
fn set_output_env_vars(driver_dir: &Path, platform: &str) {
println!(
"cargo:rustc-env=PLAYWRIGHT_DRIVER_DIR={}",
driver_dir.display()
);
println!(
"cargo:rustc-env=PLAYWRIGHT_DRIVER_VERSION={}",
PLAYWRIGHT_VERSION
);
println!("cargo:rustc-env=PLAYWRIGHT_DRIVER_PLATFORM={}", platform);
let node_exe = if cfg!(windows) {
driver_dir.join("node.exe")
} else {
driver_dir.join("node")
};
if node_exe.exists() {
println!("cargo:rustc-env=PLAYWRIGHT_NODE_EXE={}", node_exe.display());
}
let cli_js = driver_dir.join("package").join("cli.js");
if cli_js.exists() {
println!("cargo:rustc-env=PLAYWRIGHT_CLI_JS={}", cli_js.display());
}
}