blob: b58a85f316247d6423e81b95abadb03999495319 [file] [log] [blame]
// 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::error;
use std::fmt::{self, Debug, Display};
use std::fs::{canonicalize, create_dir_all, remove_dir_all, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
const DEFAULT_BINDINGS_DIR: &str = "src/bindings";
pub enum Error {
CleanupFailed,
CommandFailed,
FilePathNotUTF8,
SourceDoesntExist(PathBuf),
WrappedError(String),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
CleanupFailed => write!(f, "Failed to cleanup old bindings."),
CommandFailed => write!(f, "Command Failed."),
FilePathNotUTF8 => write!(f, "File path is not valid UTF-8."),
SourceDoesntExist(file) => write!(f, "Source file doesn't exist: {:?}", file),
WrappedError(err) => write!(f, "{}", err),
}
}
}
impl Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Forward to Display.
write!(f, "{}", self)
}
}
impl<T: error::Error> From<T> for Error {
fn from(err: T) -> Self {
Error::WrappedError(format!("{:?}", err))
}
}
// sub_modules: &[(<module name>, <relative path to source xml>), ...]
pub fn generate_module(source_dir: &Path, sub_modules: &[(&str, &str)]) -> Result<(), Error> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let bindings_dir = PathBuf::from(DEFAULT_BINDINGS_DIR);
let mut errors: String = String::new();
// Clear the bindings unless this is a release build.
if bindings_dir.exists() && Ok("release".to_string()) != env::var("PROFILE") {
remove_dir_all(&bindings_dir).or(Err(Error::CleanupFailed))?;
};
create_dir_all(&bindings_dir)?;
// Write header of include file.
let mut mod_out = File::create(out_dir.join("include_modules.rs")).unwrap();
writeln!(
mod_out,
r#"// Do not edit. This is generated by system_api/build.rs.
"#
)?;
for (module, source) in sub_modules {
// Generate bindings if they don't already exist.
let destination = bindings_dir.join(format!("{}.rs", module));
if !destination.exists() {
if let Err(err) = generate_bindings(&source_dir.join(source), &destination) {
errors.push_str(&format!(
"Failed to generate {:?} from {:?}: {}\n",
module, source, err
));
// Canonicalize will fail if the destination doesn't exist.
continue;
}
}
// Include generated bindings.
writeln!(
mod_out,
"#[path = \"{file}\"]\npub mod {module};\npub use {module}::*;",
file = canonicalize(destination)?
.to_str()
.ok_or(Error::FilePathNotUTF8)?,
module = module
)?;
}
if errors.is_empty() {
Ok(())
} else {
Err(Error::WrappedError(errors))
}
}
fn generate_bindings(source: &Path, destination: &Path) -> Result<(), Error> {
if !source.exists() {
return Err(Error::SourceDoesntExist(source.to_path_buf()));
}
let status = Command::new("dbus-codegen-rust")
.arg("-s") // Connect to system bus.
.stdin(File::open(source)?)
.stdout(File::create(destination)?)
.spawn()?
.wait()?;
if !status.success() {
return Err(Error::CommandFailed);
}
Ok(())
}