blob: 753691bed9202f66868d04da8f547af605106348 [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::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(())
}