1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Build-time evaluated expressions
//!
//! `cconst` allows defining constants at build time of any type that
//! implements the `Copy` trait. Values are generated through `build.rs`:
//!
//! ```
//! // build.rs
//! #[macro_use]
//! extern crate cconst;
//!
//! use std::net::Ipv4Addr;
//!
//! let mut cs = CopyConsts::new();
//! cs.add_const("default_ns", "::std::net::Ipv4Addr", {
//!     Ipv4Addr::new(8, 8, 8, 8)
//! });
//! cs.write_code().unwrap();
//!
//! ```
//!
//! Once set up, they can be included using the `cconst!` macro:
//!
//! ```ignore
//! // main.rs
//! #[macro_use]
//! extern crate cconst;
//!
//! include!(cconst!(default_ns));
//!
//! fn main() {
//!     println!("default_ns: {:?}", default_ns());
//! }
//! ```
//!
//! # Internals
//!
//! `cconst` works by serializing the value defined in `build.rs` into
//! byte-slice literals and including those where applicable. The example above
//! results in roughly the following generated code:
//!
//! ```ignore
//! #[inline]
//! fn default_ns() -> &'static ::std::net::Ipv4Addr {
//!     const BUF: &[u8] = &[0x08, 0x08, 0x08, 0x08, ];
//!     unsafe { &*(BUF.as_ptr() as *const ::std::net::Ipv4Addr) }
//! }
//! ```
//!
//! Calling `default_ns()` should result in an inlined pointer cast and little,
//! if any overhead.
//!
//! ## Caveats
//!
//! Due to the nature of the code generation used, the type supplied to the
//! `add_const!`-macro should be fully qualified, i.e. start with a `::`. If
//! not, it must be visible at the `include!` call site.
//!
//! While `Copy` indicates that the type can freely be copied, if any resources
//! are held by the type outside of what the compiler knows, the type cannot be
//! compile generated.
//!
//! Types that have the same names but are different will result in undefined
//! behaviour with possibly catastrophic results. This will most likely occur
//! when the `build_dependencies` and `dependencies` versions of a required
//! crate differ.
//!
//! ## TODO
//!
//! * [ ] `#[no_std]` support

/// Imports a stored constant
#[macro_export]
macro_rules! cconst {
    ($fname:ident) => (concat!(env!("OUT_DIR"), "/cconst-", stringify!($fname), ".rs"))
}

/// Creates a constant for inclusion using `cconst!`.
///
/// This macro should be preferred over `CopyConsts::add_const`, as it provides
/// additional type safety.
#[macro_export]
macro_rules! add_const {
    ($cconsts:expr, $fname: expr, $ctype:ty, $val:expr) => (
        let mat: $ctype = $val;
        $cconsts.add_const($fname, stringify!($ctype), &mat);
        )
}

use std::{collections, env, fs, io};
use std::io::Write;
use std::mem::size_of;

fn marshall_value<T: Copy>(val: &T) -> String {
    let vptr = val as *const _ as *const u8;

    let mut rexpr = String::new();
    rexpr += "&[";

    for i in 0..size_of::<T>() {
        rexpr.push_str(&format!("0x{:02X}, ", unsafe { *vptr.offset(i as isize) }));
    }

    rexpr += "]";

    rexpr
}

fn create_constant_func<T: Copy>(fname: &str, typename: &str, val: &T) -> String {
    let sval = marshall_value(val);

    format!("#[inline]\nfn {}() -> &'static {} {{
    const BUF: &[u8] = {};
    unsafe {{ &*(BUF.as_ptr() as *const {}) }}
}}\n",
            fname,
            typename,
            sval,
            typename)
}

/// Manage `build.rs` constructed constants
pub struct CopyConsts(collections::HashMap<String, String>);


fn build_output_path(fname: &str) -> Result<String, env::VarError> {
    Ok(env::var("OUT_DIR")? + "/cconst-" + fname + ".rs")
}

impl CopyConsts {
    /// Create new set of compile time functions
    pub fn new() -> CopyConsts {
        CopyConsts(collections::HashMap::new())
    }

    /// Add constant
    ///
    /// Adds a value to be stored as a compile time constant, with an internal
    /// name of `fname`.
    ///
    /// `typename` is required to output generated code, but not checked. For
    /// this reason using the `add_const!` macro instead of this function
    /// should be preferred.
    pub fn add_const<T: Copy>(&mut self, fname: &str, typename: &str, val: &T) {
        self.0
            .insert(fname.to_owned(), create_constant_func(fname, typename, val));
    }

    /// Write out code for compile-time constant generation.
    pub fn write_code(&self) -> io::Result<()> {
        for (fname, buf) in &self.0 {
            let output_path =
                build_output_path(fname)
                    .map_err(|_| io::Error::new(io::ErrorKind::Other, "missing OUT_PATH"))?;

            write!(io::stdout(), "OUTPUT PATH {:?}", output_path).unwrap();
            let mut fp = fs::File::create(output_path)?;
            fp.write_all(buf.as_bytes())?;
        }

        Ok(())
    }
}