use std::env;
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
let mut dawn_json: Option<PathBuf> = None;
let mut dawn_wire_json: Option<PathBuf> = None;
let mut api_header: Option<PathBuf> = None;
let mut out_dir: Option<PathBuf> = None;
let mut target_os: Option<String> = None;
let mut target_arch: Option<String> = None;
let mut tags: Vec<String> = Vec::new();
let mut clang_args: Vec<String> = Vec::new();
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--dawn-json" => {
dawn_json = args.next().map(PathBuf::from);
}
"--api-header" => {
api_header = args.next().map(PathBuf::from);
}
"--dawn-wire-json" => {
dawn_wire_json = args.next().map(PathBuf::from);
}
"--out-dir" => {
out_dir = args.next().map(PathBuf::from);
}
"--target-os" => {
target_os = args.next().map(|v| v.trim().to_string());
}
"--target-arch" => {
target_arch = args.next().map(|v| v.trim().to_string());
}
"--tags" => {
if let Some(value) = args.next() {
tags = value
.split(',')
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty())
.collect();
}
}
"--clang-arg" => {
if let Some(value) = args.next() {
clang_args.push(value);
}
}
_ => {
eprintln!("Unknown argument: {}", arg);
std::process::exit(2);
}
}
}
let dawn_json = dawn_json.expect("--dawn-json is required");
let api_header = api_header.expect("--api-header is required");
let out_dir = out_dir.expect("--out-dir is required");
let target_os = target_os.unwrap_or_else(|| env::consts::OS.to_string());
let target_arch = target_arch.unwrap_or_else(|| env::consts::ARCH.to_string());
validate_target(&target_os, &target_arch);
if dawn_wire_json.is_some() {
eprintln!(
"warning: --dawn-wire-json is ignored; dawn-rs no longer generates Rust wire protocol code"
);
}
let src_dir = out_dir
.parent()
.expect("--out-dir should point inside the src directory");
let ffi_dir = src_dir.join("ffi");
let api = dawn_codegen::DawnJsonParser::parse_file(&dawn_json).expect("parse dawn.json");
let filtered = if tags.is_empty() {
api
} else {
api.filter_by_tags(&tags)
};
let model = dawn_codegen::ApiModel::from_api(&filtered);
let files = dawn_codegen::generate_strings(&model);
std::fs::create_dir_all(&out_dir).expect("create output dir");
cleanup_old_layout(&out_dir).expect("cleanup old generated layout");
fs::create_dir_all(&ffi_dir).expect("create ffi module dir");
let ffi_out = ffi_dir.join(format!("{target_os}_{target_arch}.rs"));
let mut combined_clang_args = clang_args;
combined_clang_args.extend(default_clang_args(&api_header));
let ffi_rs = dawn_codegen::generate_ffi_string(&api_header, &combined_clang_args)
.expect("generate ffi.rs");
std::fs::write(&ffi_out, ffi_rs).expect("write ffi bindings");
write_generated_single_file(&out_dir, &target_os, &target_arch, &files)
.expect("write generated single file");
write_generated_wrapper(&out_dir).expect("write generated wrapper");
write_ffi_wrapper(&ffi_dir).expect("write ffi wrapper");
}
fn validate_target(target_os: &str, target_arch: &str) {
let ok_os = matches!(target_os, "linux" | "macos" | "windows");
let ok_arch = matches!(target_arch, "x86_64" | "aarch64");
if !ok_os || !ok_arch {
eprintln!(
"Unsupported target pair: {target_os}_{target_arch}. Expected OS in [linux, macos, windows] and arch in [x86_64, aarch64]."
);
std::process::exit(2);
}
}
fn default_clang_args(api_header: &Path) -> Vec<String> {
let mut args = Vec::new();
if let Some(include_dir) = api_header.parent().and_then(|p| p.parent()) {
args.push(format!("-I{}", include_dir.display()));
}
args
}
fn cleanup_old_layout(out_dir: &Path) -> std::io::Result<()> {
let targets_dir = out_dir.join("targets");
if targets_dir.exists() {
fs::remove_dir_all(targets_dir)?;
}
let ffi_dir = out_dir.join("ffi");
if ffi_dir.exists() {
fs::remove_dir_all(ffi_dir)?;
}
let old_generated_ffi = out_dir.join("ffi.rs");
if old_generated_ffi.exists() {
fs::remove_file(old_generated_ffi)?;
}
let src_dir = out_dir
.parent()
.expect("--out-dir should point inside the src directory");
let old_root_ffi = src_dir.join("ffi.rs");
if old_root_ffi.exists() {
fs::remove_file(old_root_ffi)?;
}
for entry in fs::read_dir(src_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
if name.starts_with("ffi_") {
fs::remove_file(path)?;
}
}
}
for entry in fs::read_dir(out_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
if name.starts_with("generated_") || name.starts_with("ffi_") {
fs::remove_file(path)?;
}
}
}
let ffi_dir = src_dir.join("ffi");
if ffi_dir.exists() {
for entry in fs::read_dir(&ffi_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
if name.starts_with("ffi_") {
fs::remove_file(path)?;
}
}
}
}
Ok(())
}
fn write_ffi_wrapper(ffi_dir: &Path) -> std::io::Result<()> {
let mut targets: Vec<(String, String)> = Vec::new();
for entry in fs::read_dir(ffi_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let file_name = match path.file_name().and_then(|s| s.to_str()) {
Some(name) => name,
None => continue,
};
let stem = match file_name.strip_suffix(".rs") {
Some(stem) => stem,
None => continue,
};
if stem == "mod" || stem.starts_with("ffi_") {
continue;
}
let (os, arch) = match stem.split_once('_') {
Some((os, arch)) if !os.is_empty() && !arch.is_empty() => {
(os.to_string(), arch.to_string())
}
_ => continue,
};
targets.push((os, arch));
}
targets.sort();
targets.dedup();
let mut out = String::new();
for (os, arch) in targets {
let module_name = format!("{}_{}", os.replace('-', "_"), arch.replace('-', "_"));
out.push_str(&format!(
r#"#[cfg(all(target_os = "{os}", target_arch = "{arch}"))]
mod {module_name};
#[cfg(all(target_os = "{os}", target_arch = "{arch}"))]
pub use {module_name}::*;
"#,
os = os,
arch = arch,
module_name = module_name,
));
}
let out = dawn_codegen::emitter::format_rust_source(&out);
fs::write(ffi_dir.join("mod.rs"), out)
}
fn write_generated_single_file(
out_dir: &Path,
target_os: &str,
target_arch: &str,
files: &dawn_codegen::GeneratedFiles,
) -> std::io::Result<()> {
let combined = build_combined_generated_source(files);
let target_file = out_dir.join(format!("{target_os}_{target_arch}.rs"));
fs::write(target_file, &combined)?;
Ok(())
}
fn write_generated_wrapper(out_dir: &Path) -> std::io::Result<()> {
let mut targets: Vec<(String, String)> = Vec::new();
for entry in fs::read_dir(out_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let file_name = match path.file_name().and_then(|s| s.to_str()) {
Some(name) => name,
None => continue,
};
let stem = match file_name.strip_suffix(".rs") {
Some(stem) => stem,
None => continue,
};
if stem == "mod"
|| stem.starts_with("ffi_")
|| stem.starts_with("generated_")
|| stem.starts_with("wire_")
{
continue;
}
let (os, arch) = match stem.split_once('_') {
Some((os, arch)) if !os.is_empty() && !arch.is_empty() => {
(os.to_string(), arch.to_string())
}
_ => continue,
};
targets.push((os, arch));
}
targets.sort();
targets.dedup();
let mut out = String::new();
for (os, arch) in targets {
let module_name = format!("{}_{}", os.replace('-', "_"), arch.replace('-', "_"));
out.push_str(&format!(
r#"#[cfg(all(target_os = "{os}", target_arch = "{arch}"))]
mod {module_name};
#[cfg(all(target_os = "{os}", target_arch = "{arch}"))]
pub use {module_name}::*;
"#,
os = os,
arch = arch,
module_name = module_name,
));
}
let out = dawn_codegen::emitter::format_rust_source(&out);
fs::write(out_dir.join("mod.rs"), out)
}
fn build_combined_generated_source(files: &dawn_codegen::GeneratedFiles) -> String {
let mut out = String::new();
out.push_str(&emit_inline_module("enums", &files.enums));
out.push_str(&emit_inline_module("structs", &files.structs));
out.push_str(&emit_inline_module("extensions", &files.extensions));
out.push_str(&emit_inline_module("objects", &files.objects));
out.push_str(&emit_inline_module("callbacks", &files.callbacks));
out.push_str(&emit_inline_module("functions", &files.functions));
out.push_str(&emit_inline_module("constants", &files.constants));
out.push_str(
r#"pub use enums::*;
pub use structs::*;
pub use extensions::*;
pub use objects::*;
pub use callbacks::*;
pub use functions::*;
pub use constants::*;
"#,
);
dawn_codegen::emitter::format_rust_source(&out)
}
fn emit_inline_module(name: &str, source: &str) -> String {
format!(
r#"mod {name} {{
{source}
}}"#,
)
}