#![warn(missing_copy_implementations, missing_debug_implementations)]
#![warn(trivial_casts, trivial_numeric_casts, unused_extern_crates)]
#![warn(unused_import_braces)]
#![warn(variant_size_differences)]
#![deny(missing_docs)]
extern crate rand;
use std::boxed::Box;
use std::default::Default;
use std::env;
use std::error::Error;
use std::fmt;
use std::fmt::Write as FormatWrite;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command};
use std::str::FromStr;
use rand::random;
use NewProbeError::*;
use CProbeError::*;
pub type CommandResult = io::Result<process::Output>;
#[derive(Debug)]
pub enum NewProbeError {
WorkDirMetadataInaccessible(io::Error),
WorkDirNotADirectory(PathBuf),
}
impl fmt::Display for NewProbeError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
WorkDirMetadataInaccessible(ref error) => {
f.write_fmt(
format_args!("NewProbeError: fs::metadata returned {}",
error)
)
}
WorkDirNotADirectory(ref path) => {
f.write_fmt(
format_args!("NewProbeError: \"{:?}\" is not a directory",
path)
)
}
}
}
}
impl Error for NewProbeError {
fn description(&self) -> &str {
match *self {
WorkDirMetadataInaccessible(..) => "could not query metadata from \
the provided work directory",
WorkDirNotADirectory(..) => "the path in this context must be a \
directory",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
WorkDirMetadataInaccessible(ref error) => Some(error),
WorkDirNotADirectory(..) => None,
}
}
}
fn output_as_string(output: &process::Output) -> String {
format!("{{ status: {:?}, stdout: {}, stderr: {} }}",
output.status, String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr))
}
pub struct CompileRunOutput {
pub compile_output: process::Output,
pub run_output: Option<process::Output>,
}
impl fmt::Debug for CompileRunOutput {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.write_fmt(
format_args!("probe_c_api::CompileRunOutput{{ \
compile output: {} \
run output: {} \
}}",
output_as_string(&self.compile_output),
self.run_output.as_ref().map_or(
"None".to_string(),
|output| output_as_string(output)))
)
}
}
impl CompileRunOutput {
pub fn successful_run_output(&self) -> CProbeResult<String> {
match self.run_output {
Some(ref run_output) => {
if run_output.status.success() {
Ok(String::from_utf8_lossy(&run_output.stdout).into_owned())
} else {
Err(RunError(self.compile_output.clone(),
run_output.clone()))
}
}
None => {
Err(CompileError(self.compile_output.clone()))
}
}
}
}
pub enum CProbeError {
IoError(io::Error),
CompileError(process::Output),
RunError(process::Output, process::Output),
OtherError(String),
}
impl fmt::Debug for CProbeError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
IoError(ref error) => {
f.write_fmt(
format_args!("IoError{{ {:?} }}", error)
)
}
CompileError(ref output) => {
f.write_fmt(
format_args!("CompileError{}", output_as_string(output))
)
}
RunError(ref compile_output, ref run_output) => {
f.write_fmt(
format_args!("RunError{{\
compile_output: {}\
run_output: {}\
}}",
output_as_string(compile_output),
output_as_string(run_output))
)
}
OtherError(ref string) => {
f.write_fmt(
format_args!("OtherError{{ {} }}",
string)
)
}
}
}
}
impl fmt::Display for CProbeError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
IoError(ref error) => {
f.write_fmt(
format_args!("I/O error: {}", error)
)
}
CompileError(ref output) => {
f.write_fmt(
format_args!("compilation error with output: {}",
output_as_string(output))
)
}
RunError(_, ref run_output) => {
f.write_fmt(
format_args!("test program error with output: {}",
output_as_string(run_output))
)
}
OtherError(ref string) => {
f.write_str(string)
}
}
}
}
impl Error for CProbeError {
fn description(&self) -> &str {
match *self {
IoError(..) => "I/O error",
CompileError(..) => "error when compiling C probe program",
RunError(..) => "error when running C probe program",
OtherError(ref string) => string,
}
}
fn cause(&self) -> Option<&Error> {
match *self {
IoError(ref error) => Some(error),
CompileError(..) | RunError(..) | OtherError(..) => None,
}
}
}
impl From<io::Error> for CProbeError {
fn from(error: io::Error) -> Self {
IoError(error)
}
}
pub type CProbeResult<T> = Result<T, CProbeError>;
pub struct Probe<'a> {
headers: Vec<String>,
work_dir: PathBuf,
compile_to: Box<Fn(&Path, &Path) -> CommandResult + 'a>,
run: Box<Fn(&Path) -> CommandResult + 'a>,
}
impl<'a> fmt::Debug for Probe<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.write_fmt(format_args!("probe_c_api::Probe in \"{:?}\"",
self.work_dir))
}
}
impl<'a> Probe<'a> {
pub fn new<C: 'a, R: 'a>(headers: Vec<String>,
work_dir: &Path,
compile_to: C,
run: R) -> Result<Probe<'a>, NewProbeError>
where C: Fn(&Path, &Path) -> CommandResult,
R: Fn(&Path) -> CommandResult {
match fs::metadata(work_dir) {
Ok(metadata) => if !metadata.is_dir() {
return Err(WorkDirNotADirectory(work_dir.to_path_buf()));
},
Err(error) => { return Err(WorkDirMetadataInaccessible(error)); }
}
Ok(Probe {
headers: headers,
work_dir: work_dir.to_path_buf(),
compile_to: Box::new(compile_to),
run: Box::new(run),
})
}
fn random_source_and_exe_paths(&self) -> (PathBuf, PathBuf) {
let random_suffix = random::<u64>();
let source_path = self.work_dir.join(&format!("source-{}.c",
random_suffix));
let exe_path = self.work_dir.join(&format!("exe-{}",
random_suffix))
.with_extension(env::consts::EXE_EXTENSION);
(source_path, exe_path)
}
pub fn check_compile(&self, source: &str) -> CommandResult {
let (source_path, exe_path) = self.random_source_and_exe_paths();
try!(write_to_new_file(&source_path, source));
let compile_output = try!((*self.compile_to)(&source_path, &exe_path));
try!(fs::remove_file(&source_path));
match fs::remove_file(&exe_path) {
Ok(..) => {}
Err(error) => {
if error.kind() != io::ErrorKind::NotFound {
return Err(error);
}
}
}
Ok(compile_output)
}
pub fn check_run(&self, source: &str) -> io::Result<CompileRunOutput> {
let (source_path, exe_path) = self.random_source_and_exe_paths();
try!(write_to_new_file(&source_path, source));
let compile_output = try!((*self.compile_to)(&source_path, &exe_path));
try!(fs::remove_file(&source_path));
let run_output;
if compile_output.status.success() {
run_output = Some(try!((*self.run)(&exe_path)));
try!(fs::remove_file(&exe_path));
} else {
run_output = None;
}
Ok(CompileRunOutput{
compile_output: compile_output,
run_output: run_output,
})
}
fn main_source_template(&self, headers: Vec<&str>, main_body: &str)
-> String {
let mut header_includes = String::new();
for header in &self.headers {
write!(&mut header_includes, "#include {}\n", header).unwrap();
}
for header in &headers {
write!(&mut header_includes, "#include {}\n", header).unwrap();
}
format!("{}\n\
int main(int argc, char **argv) {{\n\
{}\n\
}}\n",
header_includes,
main_body)
}
fn run_to_get_rust_constant<T: FromStr>(&self,
headers: Vec<&str>,
main_body: &str)
-> CProbeResult<T> {
let source = self.main_source_template(headers, &main_body);
let compile_run_output = try!(self.check_run(&source));
let run_out_string = try!(compile_run_output.successful_run_output());
match FromStr::from_str(run_out_string.trim()) {
Ok(size) => Ok(size),
Err(..) => Err(OtherError("unexpected output from probe program"
.to_string())),
}
}
pub fn size_of(&self, type_: &str) -> CProbeResult<usize> {
let headers: Vec<&str> = vec!["<stdio.h>"];
let main_body = format!("printf(\"%zd\\n\", sizeof({}));\n\
return 0;",
type_);
self.run_to_get_rust_constant(headers, &main_body)
}
pub fn align_of(&self, type_: &str) -> CProbeResult<usize> {
let headers: Vec<&str> = vec!["<stdio.h>", "<stdalign.h>"];
let main_body = format!("printf(\"%zd\\n\", alignof({}));\n\
return 0;",
type_);
self.run_to_get_rust_constant(headers, &main_body)
}
pub fn is_defined_macro(&self, token: &str) -> CProbeResult<bool> {
let headers: Vec<&str> = vec!["<stdio.h>"];
let main_body = format!("#ifdef {}\n\
printf(\"true\");\n\
#else\n\
printf(\"false\");\n\
#endif\n\
return 0;",
token);
self.run_to_get_rust_constant(headers, &main_body)
}
pub fn is_signed(&self, type_: &str) -> CProbeResult<bool> {
let headers: Vec<&str> = vec!["<stdio.h>"];
let main_body = format!("if ((({})-1) < 0) {{\n\
printf(\"true\");\n\
}} else {{\n\
printf(\"false\");\n\
}}\n\
return 0;",
type_);
self.run_to_get_rust_constant(headers, &main_body)
}
}
fn write_to_new_file(path: &Path, text: &str) -> io::Result<()> {
let mut file = try!(fs::File::create(path));
write!(&mut file, "{}", text)
}
impl Default for Probe<'static> {
fn default() -> Self {
Probe::new(
vec![],
&env::temp_dir(),
|source_path, exe_path| {
Command::new("gcc").arg(source_path)
.arg("-o").arg(exe_path)
.output()
},
|exe_path| {
Command::new(exe_path).output()
},
).unwrap()
}
}