probe_c_api/lib.rs
1// Copyright 2015 Sean Patrick Santos
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # About `probe-c-api`
16//!
17//! The `probe-c-api` library creates and runs short test scripts to examine the
18//! representation of types and the representations and values of constants in a
19//! C library's API. The main goal is to assist Rust-based build systems,
20//! especially Cargo build scripts, in producing bindings to C libraries
21//! automatically. Nonetheless, this approach may be extended in the nebulous
22//! future, e.g. by probing other aspects of a C API, by probing features in
23//! other C-like languages (especially C++), or by adding features to aid in
24//! writing bindings for other languages.
25//!
26//! # Source file encoding
27//!
28//! Currently, all strings corresponding to C source code are represented as
29//! UTF-8. If this is a problem, the user may modify the compile and run
30//! functions commands to do appropriate translation.
31
32#![warn(missing_copy_implementations, missing_debug_implementations)]
33#![warn(trivial_casts, trivial_numeric_casts, unused_extern_crates)]
34#![warn(unused_import_braces)]
35#![warn(variant_size_differences)]
36#![deny(missing_docs)]
37
38extern crate rand;
39
40use std::boxed::Box;
41use std::default::Default;
42use std::env;
43use std::error::Error;
44use std::fmt;
45use std::fmt::Write as FormatWrite;
46use std::fs;
47use std::io::{self, Write};
48use std::path::{Path, PathBuf};
49use std::process::{self, Command};
50use std::str::FromStr;
51
52use rand::random;
53
54use NewProbeError::*;
55use CProbeError::*;
56
57// FIXME? It's not clear whether simply aliasing the standard library types will
58// provide the functionality we want from `CommandResult`, so we could hedge our
59// bets by making `CommandResult` an opaque wrapper, leaving us the freedom to
60// change the representation later.
61//
62// On the other hand, we definitely want it to be easy to construct a
63// `CommandResult` from an `io::Result<process::Output>`, even if they aren't
64// identical types. Also, the standard library types are actually quite good for
65// this purpose, and it's not clear what more we could want!
66
67/// Result of compilation and run commands. Currently just an alias for the
68/// standard library types used by `std::process`, since in most cases we only
69/// want to know:
70///
71/// 1. Were we able to run the command at all? (If not, we'll have
72/// `io::Result::Err(..)`, probably of kind `NotFound` or
73/// `PermissionDenied`.)
74///
75/// 2. If so, did the command exit with an error? (Check `status` on
76/// `process::Output`.)
77///
78/// 3. And what did the command report? (Check `stdout` and `stderr` on
79/// `process::Output`.)
80pub type CommandResult = io::Result<process::Output>;
81
82/// Errors that can occur during probe construction.
83#[derive(Debug)]
84pub enum NewProbeError {
85 /// Error returned if we cannot get the metadata for a work directory.
86 WorkDirMetadataInaccessible(io::Error),
87 /// Error returned if the path given for a work directory does not actually
88 /// correspond to a directory.
89 WorkDirNotADirectory(PathBuf),
90}
91
92impl fmt::Display for NewProbeError {
93 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
94 match *self {
95 WorkDirMetadataInaccessible(ref error) => {
96 f.write_fmt(
97 format_args!("NewProbeError: fs::metadata returned {}",
98 error)
99 )
100 }
101 WorkDirNotADirectory(ref path) => {
102 f.write_fmt(
103 format_args!("NewProbeError: \"{:?}\" is not a directory",
104 path)
105 )
106 }
107 }
108 }
109}
110
111impl Error for NewProbeError {
112 fn description(&self) -> &str {
113 match *self {
114 WorkDirMetadataInaccessible(..) => "could not query metadata from \
115 the provided work directory",
116 WorkDirNotADirectory(..) => "the path in this context must be a \
117 directory",
118 }
119 }
120 fn cause(&self) -> Option<&Error> {
121 match *self {
122 WorkDirMetadataInaccessible(ref error) => Some(error),
123 WorkDirNotADirectory(..) => None,
124 }
125 }
126}
127
128// Utility to print `process::Output` in a human-readable form.
129fn output_as_string(output: &process::Output) -> String {
130 format!("{{ status: {:?}, stdout: {}, stderr: {} }}",
131 output.status, String::from_utf8_lossy(&output.stdout),
132 String::from_utf8_lossy(&output.stderr))
133}
134
135/// Outputs of both compilation and running.
136pub struct CompileRunOutput {
137 /// Output of the compilation phase.
138 pub compile_output: process::Output,
139 /// Output of the run phase. It is optional because if the compilation
140 /// failed, we won't try to run at all.
141 pub run_output: Option<process::Output>,
142}
143
144impl fmt::Debug for CompileRunOutput {
145 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
146 f.write_fmt(
147 format_args!("probe_c_api::CompileRunOutput{{ \
148 compile output: {} \
149 run output: {} \
150 }}",
151 output_as_string(&self.compile_output),
152 self.run_output.as_ref().map_or(
153 "None".to_string(),
154 |output| output_as_string(output)))
155 )
156 }
157}
158
159impl CompileRunOutput {
160 /// Returns a probe program's standard output as a UTF-8 string.
161 ///
162 /// This function does not panic. If the compilation or run failed, this is
163 /// reported in the error. If the program's output is not valid UTF-8, lossy
164 /// conversion is performed.
165 pub fn successful_run_output(&self) -> CProbeResult<String> {
166 match self.run_output {
167 Some(ref run_output) => {
168 if run_output.status.success() {
169 Ok(String::from_utf8_lossy(&run_output.stdout).into_owned())
170 } else {
171 Err(RunError(self.compile_output.clone(),
172 run_output.clone()))
173 }
174 }
175 None => {
176 Err(CompileError(self.compile_output.clone()))
177 }
178 }
179 }
180}
181
182// FIXME! In general there could be a lot more testing of error paths. The
183// simplest way to do this would be to create a `Probe` that spoofs `Output`s
184// that trigger each of these errors.
185
186/// Error type used when a C API probing program fails to compile or run.
187pub enum CProbeError {
188 /// An I/O error prevented the operation from continuing.
189 IoError(io::Error),
190 /// Compilation failed.
191 CompileError(process::Output),
192 /// The probing program failed when run. The compilation output is included
193 /// to assist debugging.
194 RunError(process::Output, process::Output),
195 /// All other errors, e.g. corrupt output from a probe program.
196 OtherError(String),
197}
198
199impl fmt::Debug for CProbeError {
200 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
201 match *self {
202 IoError(ref error) => {
203 f.write_fmt(
204 format_args!("IoError{{ {:?} }}", error)
205 )
206 }
207 CompileError(ref output) => {
208 f.write_fmt(
209 format_args!("CompileError{}", output_as_string(output))
210 )
211 }
212 RunError(ref compile_output, ref run_output) => {
213 f.write_fmt(
214 format_args!("RunError{{\
215 compile_output: {}\
216 run_output: {}\
217 }}",
218 output_as_string(compile_output),
219 output_as_string(run_output))
220 )
221 }
222 OtherError(ref string) => {
223 f.write_fmt(
224 format_args!("OtherError{{ {} }}",
225 string)
226 )
227 }
228 }
229 }
230}
231
232impl fmt::Display for CProbeError {
233 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
234 match *self {
235 IoError(ref error) => {
236 f.write_fmt(
237 format_args!("I/O error: {}", error)
238 )
239 }
240 CompileError(ref output) => {
241 f.write_fmt(
242 format_args!("compilation error with output: {}",
243 output_as_string(output))
244 )
245 }
246 RunError(_, ref run_output) => {
247 f.write_fmt(
248 format_args!("test program error with output: {}",
249 output_as_string(run_output))
250 )
251 }
252 OtherError(ref string) => {
253 f.write_str(string)
254 }
255 }
256 }
257}
258
259impl Error for CProbeError {
260 fn description(&self) -> &str {
261 match *self {
262 IoError(..) => "I/O error",
263 CompileError(..) => "error when compiling C probe program",
264 RunError(..) => "error when running C probe program",
265 OtherError(ref string) => string,
266 }
267 }
268 fn cause(&self) -> Option<&Error> {
269 match *self {
270 IoError(ref error) => Some(error),
271 CompileError(..) | RunError(..) | OtherError(..) => None,
272 }
273 }
274}
275
276impl From<io::Error> for CProbeError {
277 fn from(error: io::Error) -> Self {
278 IoError(error)
279 }
280}
281
282/// Result type from most functions that create C probing programs.
283pub type CProbeResult<T> = Result<T, CProbeError>;
284
285/// A struct that stores information about how to compile and run test programs.
286///
287/// The main functionality of `probe_c_api` is implemented using the methods on
288/// `Probe`. The lifetime parameter is provided in order to allow closures to be
289/// used for the compiler and run commands. If `'static` types implementing `Fn`
290/// are used (e.g. function pointers), the lifetime may be `'static`.
291pub struct Probe<'a> {
292 headers: Vec<String>,
293 work_dir: PathBuf,
294 compile_to: Box<Fn(&Path, &Path) -> CommandResult + 'a>,
295 run: Box<Fn(&Path) -> CommandResult + 'a>,
296}
297
298impl<'a> fmt::Debug for Probe<'a> {
299 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
300 f.write_fmt(format_args!("probe_c_api::Probe in \"{:?}\"",
301 self.work_dir))
302 }
303}
304
305impl<'a> Probe<'a> {
306 /// Construct a `Probe` by specifying a work directory, a method to compile
307 /// a C program, and a method to run a C program.
308 ///
309 /// The `headers` argument is a vector of headers to include in every C
310 /// program written by this probe. Each header should have the `<>` or `""`
311 /// delimiters surrounding it.
312 ///
313 /// The `work_dir` argument should be a path to a directory where the probe
314 /// can read, write, and execute files. We could attempt to verify this, but
315 /// in practice there are too many platforms and security measures out
316 /// there. So it is up to the user to figure out the difference between a
317 /// failure of a test and an inability to run the test due to security
318 /// measures.
319 ///
320 /// Files in the `work_dir` are kept from colliding via random number
321 /// generator, which makes it possible to execute tests in parallel, in
322 /// practice.
323 ///
324 /// The `compile_to` argument is responsible for taking a source file
325 /// `&Path` (the first argument) and producing a runnable program at another
326 /// `&Path` (the second argument). This is roughly equivalent to the shell
327 /// script:
328 ///
329 /// ```sh
330 /// gcc -c $1 -o $2
331 /// ```
332 ///
333 /// `compile_to` should yield a `CommandResult`, which allows the exit
334 /// status to be checked, and provides the standard output and error for
335 /// debugging purposes.
336 ///
337 /// The `run` argument is responsible for running the process and yielding
338 /// its status and output, again as a `CommandResult`.
339 ///
340 /// FIXME! Suggestions for equivalent non-POSIX examples, especially
341 /// anything relevant for Windows, are welcomed.
342 pub fn new<C: 'a, R: 'a>(headers: Vec<String>,
343 work_dir: &Path,
344 compile_to: C,
345 run: R) -> Result<Probe<'a>, NewProbeError>
346 where C: Fn(&Path, &Path) -> CommandResult,
347 R: Fn(&Path) -> CommandResult {
348 match fs::metadata(work_dir) {
349 Ok(metadata) => if !metadata.is_dir() {
350 return Err(WorkDirNotADirectory(work_dir.to_path_buf()));
351 },
352 Err(error) => { return Err(WorkDirMetadataInaccessible(error)); }
353 }
354 Ok(Probe {
355 headers: headers,
356 work_dir: work_dir.to_path_buf(),
357 compile_to: Box::new(compile_to),
358 run: Box::new(run),
359 })
360 }
361
362 // Create random paths for compilation input/output. This is intended
363 // primarily to prevent two concurrently running probes from using each
364 // others' files.
365 fn random_source_and_exe_paths(&self) -> (PathBuf, PathBuf) {
366 let random_suffix = random::<u64>();
367 let source_path = self.work_dir.join(&format!("source-{}.c",
368 random_suffix));
369 let exe_path = self.work_dir.join(&format!("exe-{}",
370 random_suffix))
371 .with_extension(env::consts::EXE_EXTENSION);
372 (source_path, exe_path)
373 }
374
375 /// Write a byte slice to a file, then attempt to compile it.
376 ///
377 /// This is not terribly useful, and is provided mostly for users who simply
378 /// want to reuse a closure that was used to construct the `Probe`, as well
379 /// as for convenience and testing of `probe-c-api` itself.
380 pub fn check_compile(&self, source: &str) -> CommandResult {
381 let (source_path, exe_path) = self.random_source_and_exe_paths();
382 try!(write_to_new_file(&source_path, source));
383 let compile_output = try!((*self.compile_to)(&source_path, &exe_path));
384 try!(fs::remove_file(&source_path));
385 // Remove the generated executable if it exists.
386 match fs::remove_file(&exe_path) {
387 Ok(..) => {}
388 Err(error) => {
389 if error.kind() != io::ErrorKind::NotFound {
390 return Err(error);
391 }
392 }
393 }
394 Ok(compile_output)
395 }
396
397 /// Write a byte slice to a file, then attempt to compile and run it.
398 ///
399 /// Like `check_compile`, this provides little value, but is available as a
400 /// minor convenience.
401 pub fn check_run(&self, source: &str) -> io::Result<CompileRunOutput> {
402 let (source_path, exe_path) = self.random_source_and_exe_paths();
403 try!(write_to_new_file(&source_path, source));
404 let compile_output = try!((*self.compile_to)(&source_path, &exe_path));
405 try!(fs::remove_file(&source_path));
406 let run_output;
407 if compile_output.status.success() {
408 run_output = Some(try!((*self.run)(&exe_path)));
409 try!(fs::remove_file(&exe_path));
410 } else {
411 run_output = None;
412 }
413 Ok(CompileRunOutput{
414 compile_output: compile_output,
415 run_output: run_output,
416 })
417 }
418
419 /// Utility for various checks that use some simple code in `main`.
420 fn main_source_template(&self, headers: Vec<&str>, main_body: &str)
421 -> String {
422 let mut header_includes = String::new();
423 for header in &self.headers {
424 write!(&mut header_includes, "#include {}\n", header).unwrap();
425 }
426 for header in &headers {
427 write!(&mut header_includes, "#include {}\n", header).unwrap();
428 }
429 format!("{}\n\
430 int main(int argc, char **argv) {{\n\
431 {}\n\
432 }}\n",
433 header_includes,
434 main_body)
435 }
436
437 /// Utility for code that simply prints a Rust constant, readable using
438 /// `FromStr::from_str`, in `main`.
439 fn run_to_get_rust_constant<T: FromStr>(&self,
440 headers: Vec<&str>,
441 main_body: &str)
442 -> CProbeResult<T> {
443 let source = self.main_source_template(headers, &main_body);
444 let compile_run_output = try!(self.check_run(&source));
445 let run_out_string = try!(compile_run_output.successful_run_output());
446 // If the program produces invalid output, we don't really check what's
447 // wrong with the output right now. Either the lossy UTF-8 conversion
448 // will produce nonsense, or we will just fail to pick out a number
449 // here.
450 match FromStr::from_str(run_out_string.trim()) {
451 Ok(size) => Ok(size),
452 Err(..) => Err(OtherError("unexpected output from probe program"
453 .to_string())),
454 }
455 }
456
457 /// Get the size of a C type, in bytes.
458 pub fn size_of(&self, type_: &str) -> CProbeResult<usize> {
459 let headers: Vec<&str> = vec!["<stdio.h>"];
460 let main_body = format!("printf(\"%zd\\n\", sizeof({}));\n\
461 return 0;",
462 type_);
463 self.run_to_get_rust_constant(headers, &main_body)
464 }
465
466 /// Get the alignment of a C type, in bytes.
467 ///
468 /// Note that this method depends on the compiler having implemented C11
469 /// alignment facilities (specifically `stdalign.h` and `alignof`).
470 pub fn align_of(&self, type_: &str) -> CProbeResult<usize> {
471 let headers: Vec<&str> = vec!["<stdio.h>", "<stdalign.h>"];
472 let main_body = format!("printf(\"%zd\\n\", alignof({}));\n\
473 return 0;",
474 type_);
475 self.run_to_get_rust_constant(headers, &main_body)
476 }
477
478 /// Check to see if a macro is defined.
479 ///
480 /// One obvious use for this is to check for macros that are intended to be
481 /// used with `#ifdef`, e.g. macros that communicate configuration options
482 /// originally used to build the library.
483 ///
484 /// A less obvious use is to check whether or not a constant or function has
485 /// been implemented as a macro, for cases where this is not specified in
486 /// the API documentation, or differs between library versions. In such
487 /// cases, bindings may have to omit functionality provided by macros, or
488 /// else implement such functionality via some special workaround.
489 pub fn is_defined_macro(&self, token: &str) -> CProbeResult<bool> {
490 let headers: Vec<&str> = vec!["<stdio.h>"];
491 let main_body = format!("#ifdef {}\n\
492 printf(\"true\");\n\
493 #else\n\
494 printf(\"false\");\n\
495 #endif\n\
496 return 0;",
497 token);
498 self.run_to_get_rust_constant(headers, &main_body)
499 }
500
501 /// Check to see if an integer type is signed or unsigned.
502 pub fn is_signed(&self, type_: &str) -> CProbeResult<bool> {
503 let headers: Vec<&str> = vec!["<stdio.h>"];
504 let main_body = format!("if ((({})-1) < 0) {{\n\
505 printf(\"true\");\n\
506 }} else {{\n\
507 printf(\"false\");\n\
508 }}\n\
509 return 0;",
510 type_);
511 self.run_to_get_rust_constant(headers, &main_body)
512 }
513}
514
515// Little utility to cat something to a new file.
516fn write_to_new_file(path: &Path, text: &str) -> io::Result<()> {
517 // FIXME? Should we try putting in tests for each potential `try!` error?
518 // It's hard to trigger them with Rust 1.0, since the standard library's
519 // filesystem permission operations haven't been stabilized yet.
520 let mut file = try!(fs::File::create(path));
521 write!(&mut file, "{}", text)
522}
523
524/// We provide a default `Probe<'static>` that runs in an OS-specific temporary
525/// directory, uses gcc, and simply runs each test.
526///
527/// # Panics
528///
529/// Panics if probe creation fails.
530///
531/// FIXME? Can we do better than the gcc command on Windows?
532impl Default for Probe<'static> {
533 fn default() -> Self {
534 Probe::new(
535 vec![],
536 &env::temp_dir(),
537 |source_path, exe_path| {
538 Command::new("gcc").arg(source_path)
539 .arg("-o").arg(exe_path)
540 .output()
541 },
542 |exe_path| {
543 Command::new(exe_path).output()
544 },
545 ).unwrap()
546 }
547}