tracers-libstapsdt-sys 0.1.0

Exposes libstapsdt to Rust code for programmatic creation of SystemTap user mode probes. Never use this crate directly; use `tracers` and let it determine the correct implementation at build time.
Documentation
extern crate cc;
extern crate pkg_config;

use failure::{bail, format_err, Fallible};
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::process::Command;

fn is_enabled() -> bool {
    env::var("CARGO_FEATURE_ENABLED").is_ok() || is_required()
}

fn is_required() -> bool {
    env::var("CARGO_FEATURE_REQUIRED").is_ok()
}

fn main() {
    //by default we don't do anything here unless this lib is explicitly enabled
    if !is_enabled() {
        println!("libstapsdt-sys is not enabled; build skipped");
        return;
    }

    let fail_on_error = is_required();

    match try_build() {
        Ok(_) => {
            //Build succeeded, which means the Rust bindings should be enabled and
            //dependent crates should be signaled that this lib is available
            println!("cargo:rustc-cfg=enabled");
            println!("cargo:succeeded=1"); //this will set DEP_(PKGNAME)_SUCCEEDED in dependent builds
        }
        Err(e) => {
            if fail_on_error {
                panic!("libstapsdt-sys build failed: {}", e);
            } else {
                println!("cargo:warning=libstapsdt-sys build failed: {}", e);
                println!(
                    "cargo:warning=the libstapsdt-sys bindings will not be included in the crate"
                );
            }
        }
    }
}

fn try_build() -> Fallible<()> {
    if env::var("CARGO_CFG_TARGET_OS")? != "linux" {
        bail!("libstapsdt-sys is only supported on Linux")
    } else if env::var("CARGO_CFG_TARGET_ARCH")? != "x86_64" {
        bail!("libstapsdt-sys is only supported on x86_64 architectures")
    }

    if env::var("DEP_ELF_SUCCEEDED").is_err() {
        bail!("libstapsdt-sys is not available because libelf-sys did not build successfully")
    }

    // Init the submodule if not already where the source code is located
    let src_path = fs::canonicalize(Path::new("vendor/libstapsdt")).unwrap();
    if !src_path.join("/.git").exists() {
        let _ = Command::new("git")
            .args(&["submodule", "update", "--init"])
            .status();
    }

    //The makefile for libstapsdt is mercifully simple, and since it's wrapping a Linux-only
    //subsystem SytemTap there's no cross-platform nonsense either.
    let dst = fs::canonicalize(PathBuf::from(env::var("OUT_DIR")?))?;
    let root = dst.join("libstapsdt");
    let build = root.join("build");
    let lib = root.join("lib");
    let include = src_path.clone(); //libstapsdt doesn't segregate include from src files

    //the libstapstd Makefile is not idempotent, it will actually fail if `make install` is run a
    //second time.  So, ensure we always build with a clean slate
    let _ = fs::remove_dir_all(&root);
    fs::create_dir_all(&build)?;
    fs::create_dir_all(&lib)?;

    //We won't use cc to build the library, it has a makefile
    //But the cc crate will help us build up the right CFLAGS
    let mut cfg = cc::Build::new();
    cfg.warnings(true)
        .warnings_into_errors(false) //it pains me to do this but the lib doesn't compile clean
        .flag("-z")
        .flag("noexecstack")
        .pic(true)
        .out_dir(&build);

    // The libelf-sys crate which wraps the libelf library will provide the path to its include
    // files.  The lib itself will already be linked via cargo magic
    cfg.include(&env::var("DEP_ELF_INCLUDE")?);
    let compiler = cfg.get_compiler();
    let mut cflags = OsString::from("-std=gnu11 ");
    for arg in compiler.args() {
        cflags.push(arg);
        cflags.push(" ");
    }

    //NB: I am deliberately overriding the CC and CFLAGS variabes hard-coded in the `Makefile` with
    //values computed using the `cc` crate.  As of this writing, that works.  It's possible a
    //future change to `libstapsdt-sys` might add more flags that will then not be reflected here
    //and break the build.  If so that's the first place to look.
    let mut make = Command::new("make");
    make.arg("--environment-overrides")
        .arg("out/libstapsdt.a")
        .env("CC", compiler.path())
        .env("CFLAGS", cflags)
        .env("PREFIX", root.clone())
        .env("VERBOSE", "1")
        .current_dir(&src_path);

    run(&mut make, "make")?;

    //The makefile doesn't copy the static lib anywhere so do that ourselves
    let libstapsdt_static_path = src_path.join("out/libstapsdt.a");
    let libstapsdt_output_path = lib.join("libstapsdt.a");
    fs::copy(libstapsdt_static_path.clone(), libstapsdt_output_path)?;

    // we must explicitly tell cargo that it should statically link with the `elf` lib which the
    // `libstapsdt-sys` crate already ensures is compiled and in the library search  path
    println!("cargo:rustc-link-lib=static={}", "elf");
    println!("cargo:rustc-link-lib=static={}", "stapsdt");
    println!("cargo:rustc-link-search=native={}", lib.display());
    println!("cargo:include={}", include.display());
    println!("cargo:root={}", root.display());

    Ok(())
}

fn run(cmd: &mut Command, program: &str) -> Fallible<()> {
    println!("running: {:?}", cmd);
    match cmd.status() {
        Ok(status) if status.success() => Ok(()),
        Ok(status) => Err(format_err!(
            "'{}' failed with status code {}",
            program,
            status
        )),
        Err(ref e) if e.kind() == ErrorKind::NotFound => Err(format_err!(
            "failed to execute command [{}] because the command was not found",
            program
        )),
        Err(e) => Err(format_err!("failed to execute command: {}", e)),
    }
}