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}