blob: b32fcaa90ef347de3fca0897b8fd31aade281310 [file] [log] [blame]
use serde::Deserialize;
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};
/// Clang AST node.
#[derive(Deserialize)]
struct Node {
kind: String,
name: Option<String>,
value: Option<String>,
#[serde(default)]
inner: Vec<Node>,
}
/// Convert "kVarName" to "VAR_NAME".
fn convert_var_name(mut name: &str) -> String {
// Drop the "k" prefix.
if name.starts_with('k') {
name = &name[1..];
}
let mut out = String::new();
for c in name.chars() {
if !out.is_empty() && c.is_uppercase() {
out.push('_')
}
out.push_str(&c.to_uppercase().to_string());
}
out
}
/// Extract the variable declarations from the AST and convert them to
/// Rust code. For example, this C++ code:
///
/// constexpr char kInstallMethod[] = "StartOsInstall";
///
/// Is converted to this Rust code:
///
/// pub const INSTALL_METHOD: &str = "StartOsInstall";
fn get_var_lines(ast: &Node) -> Option<String> {
// Find the os_install_service namespace.
let namespace_node = ast.inner.iter().find(|node| {
node.kind == "NamespaceDecl" && node.name == Some("os_install_service".to_string())
})?;
// Get an iterator of (var-name, var-value) pairs.
let vars = namespace_node.inner.iter().filter_map(|node| {
if node.kind == "VarDecl" && node.inner.len() == 1 {
let name = node.name.as_ref()?;
let value = node.inner[0].value.as_ref()?;
Some((convert_var_name(name), value))
} else {
None
}
});
let var_lines: Vec<_> = vars
.map(|(name, val)| format!("pub const {}: &str = {};", name, val))
.collect();
Some(var_lines.join("\n"))
}
/// Get the directory containing the dbus headers. This is needed so
/// that standalone "cargo build" works as well as building in the
/// chroot.
fn get_system_api_dir() -> PathBuf {
match env::var("SYSROOT") {
Ok(path) => PathBuf::from(path).join("usr/include/chromeos"),
Err(_) => PathBuf::from("../system_api"),
}
}
/// Get the name of the clang++ executable. This is needed so that
/// standalone "cargo build" works as well as building in the chroot.
fn get_clang_name() -> String {
match env::var("CBUILD") {
Ok(cbuild) => format!("{}-clang++", cbuild),
Err(_) => "clang++".to_owned(),
}
}
fn main() {
let header_path = get_system_api_dir().join("dbus/os_install_service/dbus-constants.h");
assert!(header_path.exists());
// Rebuild if the header changes.
println!("cargo:rerun-if-changed={}", header_path.display());
// Use clang to get an AST in JSON.
let output = Command::new(get_clang_name())
.args(&["-Xclang", "-ast-dump=json", "-fsyntax-only"])
.arg(header_path)
.output()
.unwrap();
assert!(output.status.success());
// Parse the JSON.
let ast: Node = serde_json::from_slice(&output.stdout).unwrap();
// Convert C++ declarations to Rust.
let lines = get_var_lines(&ast).unwrap();
// Write the Rust code to $OUT_DIR/dbus_constants.rs.
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
fs::write(out_dir.join("dbus_constants.rs"), lines).unwrap();
}