pub mod codegen;
pub mod codegen_types;
mod pointer_constants;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
pub(crate) fn convert_io_err(err: std::io::Error) -> capnp::Error {
use std::io;
let kind = match err.kind() {
io::ErrorKind::TimedOut => capnp::ErrorKind::Overloaded,
io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionRefused
| io::ErrorKind::ConnectionReset
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::NotConnected => capnp::ErrorKind::Disconnected,
_ => capnp::ErrorKind::Failed,
};
capnp::Error {
extra: format!("{err}"),
kind,
}
}
fn run_command(
mut command: ::std::process::Command,
mut code_generation_command: codegen::CodeGenerationCommand,
) -> ::capnp::Result<()> {
let mut p = command.spawn().map_err(convert_io_err)?;
code_generation_command.run(p.stdout.take().unwrap())?;
let exit_status = p.wait().map_err(convert_io_err)?;
if !exit_status.success() {
Err(::capnp::Error::failed(format!(
"Non-success exit status: {exit_status}"
)))
} else {
Ok(())
}
}
#[derive(Default)]
pub struct CompilerCommand {
files: Vec<PathBuf>,
src_prefixes: Vec<PathBuf>,
import_paths: Vec<PathBuf>,
no_standard_import: bool,
executable_path: Option<PathBuf>,
output_path: Option<PathBuf>,
default_parent_module: Vec<String>,
raw_code_generator_request_path: Option<PathBuf>,
crate_provides_map: HashMap<u64, String>,
}
impl CompilerCommand {
pub fn new() -> Self {
Self::default()
}
pub fn file<P>(&mut self, path: P) -> &mut Self
where
P: AsRef<Path>,
{
self.files.push(path.as_ref().to_path_buf());
self
}
pub fn src_prefix<P>(&mut self, prefix: P) -> &mut Self
where
P: AsRef<Path>,
{
self.src_prefixes.push(prefix.as_ref().to_path_buf());
self
}
pub fn import_path<P>(&mut self, dir: P) -> &mut Self
where
P: AsRef<Path>,
{
self.import_paths.push(dir.as_ref().to_path_buf());
self
}
pub fn crate_provides(
&mut self,
crate_name: impl Into<String>,
files: impl IntoIterator<Item = u64>,
) -> &mut Self {
let crate_name = crate_name.into();
for file in files.into_iter() {
self.crate_provides_map.insert(file, crate_name.clone());
}
self
}
pub fn no_standard_import(&mut self) -> &mut Self {
self.no_standard_import = true;
self
}
pub fn output_path<P>(&mut self, path: P) -> &mut Self
where
P: AsRef<Path>,
{
self.output_path = Some(path.as_ref().to_path_buf());
self
}
pub fn capnp_executable<P>(&mut self, path: P) -> &mut Self
where
P: AsRef<Path>,
{
self.executable_path = Some(path.as_ref().to_path_buf());
self
}
fn new_command(&self) -> ::std::process::Command {
if let Some(executable) = &self.executable_path {
::std::process::Command::new(executable)
} else {
::std::process::Command::new("capnp")
}
}
pub fn default_parent_module(&mut self, default_parent_module: Vec<String>) -> &mut Self {
self.default_parent_module = default_parent_module;
self
}
pub fn raw_code_generator_request_path<P>(&mut self, path: P) -> &mut Self
where
P: AsRef<Path>,
{
self.raw_code_generator_request_path = Some(path.as_ref().to_path_buf());
self
}
pub fn run(&mut self) -> ::capnp::Result<()> {
match self.new_command().arg("--version").output() {
Err(error) => {
return Err(::capnp::Error::failed(format!(
"Failed to execute `capnp --version`: {error}. \
Please verify that version 0.5.2 or higher of the capnp executable \
is installed on your system. See https://capnproto.org/install.html"
)))
}
Ok(output) => {
if !output.status.success() {
return Err(::capnp::Error::failed(format!(
"`capnp --version` returned an error: {:?}. \
Please verify that version 0.5.2 or higher of the capnp executable \
is installed on your system. See https://capnproto.org/install.html",
output.status
)));
}
}
}
let mut command = self.new_command();
command.env_remove("PWD");
command.arg("compile").arg("-o").arg("-");
if self.no_standard_import {
command.arg("--no-standard-import");
}
for import_path in &self.import_paths {
command.arg(&format!("--import-path={}", import_path.display()));
}
for src_prefix in &self.src_prefixes {
command.arg(&format!("--src-prefix={}", src_prefix.display()));
}
for file in &self.files {
std::fs::metadata(file).map_err(|error| {
let current_dir = match std::env::current_dir() {
Ok(current_dir) => format!("`{}`", current_dir.display()),
Err(..) => "<unknown working directory>".to_string(),
};
::capnp::Error::failed(format!(
"Unable to stat capnp input file `{}` in working directory {}: {}. \
Please check that the file exists and is accessible for read.",
file.display(),
current_dir,
error
))
})?;
command.arg(file);
}
let output_path = if let Some(output_path) = &self.output_path {
output_path.clone()
} else {
PathBuf::from(::std::env::var("OUT_DIR").map_err(|error| {
::capnp::Error::failed(format!(
"Could not access `OUT_DIR` environment variable: {error}. \
You might need to set it up or instead create you own output \
structure using `CompilerCommand::output_path`"
))
})?)
};
command.stdout(::std::process::Stdio::piped());
command.stderr(::std::process::Stdio::inherit());
let mut code_generation_command = crate::codegen::CodeGenerationCommand::new();
code_generation_command
.output_directory(output_path)
.default_parent_module(self.default_parent_module.clone())
.crates_provide_map(self.crate_provides_map.clone());
if let Some(raw_code_generator_request_path) = &self.raw_code_generator_request_path {
code_generation_command
.raw_code_generator_request_path(raw_code_generator_request_path.clone());
}
run_command(command, code_generation_command).map_err(|error| {
::capnp::Error::failed(format!(
"Error while trying to execute `capnp compile`: {error}."
))
})
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn compiler_command_new_no_out_dir() {
std::env::remove_var("OUT_DIR");
let error = CompilerCommand::new().run().unwrap_err().extra;
assert!(error.starts_with("Could not access `OUT_DIR` environment variable"));
}