| 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(); |
| } |