| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Provides tools for generating a Rust library with D-Bus bindings. The generated bindings are |
| // included in the published crate since the source XML files are only available from the original |
| // path or the ebuild. See the README.md for usage. |
| |
| use std::env; |
| use std::fmt::{self, Debug, Display}; |
| use std::fs::{create_dir_all, remove_dir_all, File}; |
| use std::io::{self, Write}; |
| use std::path::{Path, PathBuf}; |
| use std::process::Command; |
| |
| const DEFAULT_BINDINGS_DIR: &str = "src/bindings"; |
| const DEFAULT_CLIENT_OPTS: &[&str] = &["-s", "-m", "None"]; |
| const DEFAULT_SERVER_OPTS: &[&str] = &["-s", "-m", "Fn", "-a", "RefClosure"]; |
| |
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
| pub enum BindingsType { |
| Client, |
| Server, |
| Both, |
| } |
| |
| #[derive(Debug)] |
| pub enum Error { |
| CleanupFailed(io::Error), |
| CommandFailed(Option<i32>), |
| Create(io::Error), |
| CreateDirAll(io::Error), |
| Generate(String), |
| Open(io::Error), |
| SourceDoesntExist(PathBuf), |
| Spawn(io::Error), |
| Wait(io::Error), |
| Write(io::Error), |
| } |
| |
| impl Display for Error { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use self::Error::*; |
| |
| match self { |
| CleanupFailed(e) => write!(f, "failed to cleanup old bindings: {}", e), |
| CommandFailed(o) => match o { |
| Some(c) => write!(f, "command failed with code {}", c), |
| None => write!(f, "command failed"), |
| }, |
| Create(e) => write!(f, "create failed: {}", e), |
| CreateDirAll(e) => write!(f, "create_dir_all failed: {}", e), |
| Generate(s) => write!(f, "generate failed: {}", s), |
| Open(e) => write!(f, "open failed: {}", e), |
| SourceDoesntExist(file) => match file.to_str() { |
| None => write!(f, "empty path"), |
| Some(s) => write!(f, "source file doesn't exist: {}", s), |
| }, |
| Spawn(e) => write!(f, "spawn failed: {}", e), |
| Wait(e) => write!(f, "wait failed: {}", e), |
| Write(e) => write!(f, "write error: {}", e), |
| } |
| } |
| } |
| |
| /// The result of an operation in this crate. |
| pub type Result<T> = std::result::Result<T, Error>; |
| |
| // Include generated bindings. |
| fn include_module(mod_out: &mut File, module: &str) -> Result<()> { |
| writeln!( |
| mod_out, |
| " pub mod {module};\n pub use {module}::*;", |
| module = module |
| ) |
| .map_err(Error::Write) |
| } |
| |
| // sub_modules: &[(<module name>, <relative path to source xml>), ...] |
| pub fn generate_module( |
| source_dir: &Path, |
| sub_modules: &[(&str, &str, BindingsType)], |
| ) -> Result<()> { |
| let bindings_dir = PathBuf::from(DEFAULT_BINDINGS_DIR); |
| let client_dir = bindings_dir.join("client"); |
| let server_dir = bindings_dir.join("server"); |
| |
| let mut errors: String = String::new(); |
| |
| // If the bindings exist and it is a release build exit early. |
| if bindings_dir.exists() { |
| if env::var("PROFILE").map_or(false, |v| &v == "release") { |
| return Ok(()); |
| } |
| remove_dir_all(&bindings_dir).map_err(Error::CleanupFailed)?; |
| } |
| create_dir_all(&client_dir).map_err(Error::CreateDirAll)?; |
| create_dir_all(&server_dir).map_err(Error::CreateDirAll)?; |
| |
| // Write header of include file. |
| let mut mod_out = |
| File::create(bindings_dir.join("include_modules.rs")).map_err(Error::Create)?; |
| writeln!( |
| mod_out, |
| r#"// Do not edit. This is generated by system_api/build.rs. |
| pub mod client {{"# |
| ) |
| .map_err(Error::Write)?; |
| |
| for (module, source, bindings_type) in sub_modules { |
| match bindings_type { |
| BindingsType::Client | BindingsType::Both => {} |
| BindingsType::Server => { |
| continue; |
| } |
| } |
| // Generate bindings if they don't already exist. |
| let destination = client_dir.join(format!("{}.rs", module)); |
| if !destination.exists() { |
| if let Err(err) = |
| generate_bindings(&source_dir.join(source), &destination, DEFAULT_CLIENT_OPTS) |
| { |
| errors.push_str(&format!( |
| "Failed to generate {:?} from {:?}: {}\n", |
| module, source, err |
| )); |
| // Canonicalize will fail if the destination doesn't exist. |
| continue; |
| } |
| } |
| include_module(&mut mod_out, module)?; |
| } |
| writeln!( |
| mod_out, |
| r#"}} |
| |
| pub mod server {{"# |
| ) |
| .map_err(Error::Write)?; |
| for (module, source, bindings_type) in sub_modules { |
| match bindings_type { |
| BindingsType::Server | BindingsType::Both => {} |
| BindingsType::Client => { |
| continue; |
| } |
| } |
| // Generate bindings if they don't already exist. |
| let destination = server_dir.join(format!("{}.rs", module)); |
| if !destination.exists() { |
| if let Err(err) = |
| generate_bindings(&source_dir.join(source), &destination, DEFAULT_SERVER_OPTS) |
| { |
| errors.push_str(&format!( |
| "Failed to generate {:?} from {:?}: {}\n", |
| module, source, err |
| )); |
| // Canonicalize will fail if the destination doesn't exist. |
| continue; |
| } |
| } |
| include_module(&mut mod_out, module)?; |
| } |
| writeln!(mod_out, "}}").map_err(Error::Write)?; |
| |
| if errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(Error::Generate(errors)) |
| } |
| } |
| |
| fn generate_bindings(source: &Path, destination: &Path, opts: &[&str]) -> Result<()> { |
| if !source.exists() { |
| return Err(Error::SourceDoesntExist(source.to_path_buf())); |
| } |
| let status = Command::new("dbus-codegen-rust") |
| .args(opts) |
| .stdin(File::open(source).map_err(Error::Open)?) |
| .stdout(File::create(destination).map_err(Error::Create)?) |
| .spawn() |
| .map_err(Error::Spawn)? |
| .wait() |
| .map_err(Error::Wait)?; |
| if !status.success() { |
| return Err(Error::CommandFailed(status.code())); |
| } |
| Ok(()) |
| } |