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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
// Copyright 2019-2020 the Tectonic Project
// Licensed under the MIT License.
//! This support crate helps deal with `CARGO_CFG_TARGET_*` variables. When
//! cross-compiling with a `build.rs` script, these variables must be used
//! instead of constructs such as `cfg!(target_arch = ...)` because the
//! build.rs compilation targets the build host architecture, not the final
//! target architecture.
//!
//! For more information, see the documentation on:
//!
//! * [cargo environment variables](https://doc.rust-lang.org/cargo/reference/environment-variables.html)
//! * [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
// Debugging help (requires nightly):
//#![feature(trace_macros)]
//trace_macros!(true);
use lazy_static::lazy_static;
lazy_static! {
pub static ref TARGET_CONFIG: TargetConfiguration = TargetConfiguration::default();
}
#[derive(Clone, Debug)]
/// Information about the compilation target.
///
/// These parameters are derived from the `CARGO_TARGET_CFG_*` environment
/// variables, which must be used to obtain correct results when
/// cross-compiling a `build.rs` script. The configuration values are
/// uppercased when they're loaded, to allow for case-insensitive comparisons
/// later.
pub struct TargetConfiguration {
pub arch: String,
pub feature: String,
pub os: String,
pub family: String,
pub env: String,
pub endian: String,
pub pointer_width: String,
pub vendor: String,
}
impl Default for TargetConfiguration {
/// Creates a TargetConfiguration from the `CARGO_CFG_TARGET_*`
/// [environment variables](https://doc.rust-lang.org/cargo/reference/environment-variables.html)
fn default() -> Self {
fn getenv(var: &'static str) -> String {
std::env::var(var)
.unwrap_or_else(|_| String::new())
.to_uppercase()
}
TargetConfiguration {
arch: getenv("CARGO_CFG_TARGET_ARCH"),
feature: getenv("CARGO_CFG_TARGET_FEATURE"),
os: getenv("CARGO_CFG_TARGET_OS"),
family: getenv("CARGO_CFG_TARGET_FAMILY"),
env: getenv("CARGO_CFG_TARGET_ENV"),
endian: getenv("CARGO_CFG_TARGET_ENDIAN"),
pointer_width: getenv("CARGO_CFG_TARGET_POINTER_WIDTH"),
vendor: getenv("CARGO_CFG_TARGET_VENDOR"),
}
}
}
impl TargetConfiguration {
/// Test whether the target architecture exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_arch(&self, arch: &str) -> bool {
self.arch == arch.to_uppercase()
}
/// Test whether the target OS exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_os(&self, os: &str) -> bool {
self.os == os.to_uppercase()
}
/// Test whether the target family exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_family(&self, family: &str) -> bool {
self.family == family.to_uppercase()
}
/// Test whether the target "environment" exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_env(&self, env: &str) -> bool {
self.env == env.to_uppercase()
}
/// Test whether the target endianness exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_endian(&self, endian: &str) -> bool {
self.endian == endian.to_uppercase()
}
/// Test whether the target pointer width exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_pointer_width(&self, pointer_width: &str) -> bool {
self.pointer_width == pointer_width.to_uppercase()
}
/// Test whether the target vendor exactly matches the argument, in
/// case-insensitive fashion.
pub fn target_vendor(&self, vendor: &str) -> bool {
self.vendor == vendor.to_uppercase()
}
}
/// Test for characteristics of the target machine.
///
/// Unlike the standard `cfg!` macro, this macro will give correct results
/// when cross-compiling in a build.rs script. It attempts, but is not
/// guaranteed, to emulate the syntax of the `cfg!` macro. Note, however,
/// that the result of the macro must be evaluated at runtime, not compile-time.
///
/// Supported syntaxes:
///
/// ```notest
/// target_cfg!(target_os = "macos");
/// target_cfg!(not(target_os = "macos"));
/// target_cfg!(any(target_os = "macos", target_endian = "big"));
/// target_cfg!(all(target_os = "macos", target_endian = "big"));
/// target_cfg!(all(target_os = "macos", not(target_endian = "big")));
/// ```
// Here we go with some exciting macro fun!
//
// Since each individual test can be evaluated to a boolean on-the-spot, the
// macro expands out to a big boolean logical expression. Fundamentally, it's
// not too hard to allow complex syntax because the macro can recurse:
//
// ```
// target_cfg!(not(whatever)) => !(target_cfg!(whatever))
// target_cfg!(any(c1, c2)) => target_cfg!(c1) || target_cfg!(c2)
// ```
//
// The core implementation challenge here is that we need to parse
// comma-separated lists where each term might contain all sorts of unexpected
// content. Within the confines of the macro_rules! formalism, this means that
// we need to scan through such comma-separated lists and group their tokens
// before actually evaluating them.
//
// Some key points to remember about how this all works:
//
// 1. A "token tree" type is either a single token or a series of tokens
// delimited by balanced delimiters such as ({[]}). As such, in order to
// match an arbitrary token sequence, you need to use repetition
// expressions of the form `$($toks:tt)+`.
// 2. The macro evaluator looks at rules in order and cannot backtrack. That
// is, if it is looking at a rule and has matched the first 5 tokens but
// the 6th disagrees, there must be a subsequent rule that also matches
// those first 5 tokens.
// 3. Given the above, we use the standard trick of having different macro
// "modes" prefixed with an expression like `@emit`. They are essentially
// different sub-macros but this trick allows us to get everything done
// with one named macro_rules! export.
// 4. Also due to the above, the logical flow of the macro generally goes from
// bottom to top, so that's probably the best way to read the code.
//
// Some links for reference:
//
// - https://users.rust-lang.org/t/top-down-macro-parsing-or-higher-order-macros/8879
// - https://danielkeep.github.io/tlborm/book/pat-incremental-tt-munchers.html
#[macro_export]
macro_rules! target_cfg {
// "@emit" rules are used for comma-separated lists that have had their
// tokens grouped. The general pattern is: `target_cfg!(@emit $operation
// {clause1..} {clause2..} {clause3..})`.
// Emitting `any(clause1,clause2,...)`: convert to `target_cfg!(clause1) && target_cfg!(clause2) && ...`
(
@emit
all
$({$($grouped:tt)+})+
) => {
($(
(target_cfg!($($grouped)+))
)&&+)
};
// Likewise for `all(clause1,clause2,...)`.
(
@emit
any
$({$($grouped:tt)+})+
) => {
($(
(target_cfg!($($grouped)+))
)||+)
};
// "@clause" rules are used to parse the comma-separated lists. They munch
// their inputs token-by-token and finally invoke an "@emit" rule when the
// list is all grouped. The general pattern for recording the parser state
// is:
//
// ```
// target_cfg!(
// @clause $operation
// [{grouped-clause-1} {grouped-clause-2...}]
// [not-yet-parsed-tokens...]
// current-clause-tokens...
// )
// ```
// This rule must come first in this section. It fires when the next token
// to parse is a comma. When this happens, we take the tokens in the
// current clause and add them to the list of grouped clauses, adding
// delimeters so that the grouping can be easily extracted again in the
// emission stage.
(
@clause
$op:ident
[$({$($grouped:tt)+})*]
[, $($rest:tt)*]
$($current:tt)+
) => {
target_cfg!(@clause $op [
$(
{$($grouped)+}
)*
{$($current)+}
] [
$($rest)*
])
};
// This rule comes next. It fires when the next un-parsed token is *not* a
// comma. In this case, we add that token to the list of tokens in the
// current clause, then move on to the next one.
(
@clause
$op:ident
[$({$($grouped:tt)+})*]
[$tok:tt $($rest:tt)*]
$($current:tt)*
) => {
target_cfg!(@clause $op [
$(
{$($grouped)+}
)*
] [
$($rest)*
] $($current)* $tok)
};
// This rule fires when there are no more tokens to parse in this list. We
// finish off the "current" token group, then delegate to the emission
// rule.
(
@clause
$op:ident
[$({$($grouped:tt)+})*]
[]
$($current:tt)+
) => {
target_cfg!(@emit $op
$(
{$($grouped)+}
)*
{$($current)+}
)
};
// Finally, these are the "toplevel" syntaxes for specific tests that can
// be performed. Any construction not prefixed with one of the magic
// tokens must match one of these.
// `all(clause1, clause2...)` : we must parse this comma-separated list and
// partner with `@emit all` to output a bunch of && terms.
(
all($($tokens:tt)+)
) => {
target_cfg!(@clause all [] [$($tokens)+])
};
// Likewise for `any(clause1, clause2...)`
(
any($($tokens:tt)+)
) => {
target_cfg!(@clause any [] [$($tokens)+])
};
// `not(clause)`: compute the inner clause, then just negate it.
(
not($($tokens:tt)+)
) => {
!(target_cfg!($($tokens)+))
};
// `param = value`: test for equality.
(
$e:tt = $v:expr
) => {
$crate::TARGET_CONFIG.$e($v)
};
}
#[cfg(test)]
mod tests {
/// Set up the environment variables for testing. We intentionally choose
/// values that don't occur in the real world, except for parameters that
/// have heavily constrained options, to avoid accidentally passing
/// if/when running the test suite on familiar hardware.
fn setup_test_env() {
std::env::set_var("CARGO_CFG_TARGET_ARCH", "testarch");
std::env::set_var("CARGO_CFG_TARGET_FEATURE", "testfeat1,testfeat2");
std::env::set_var("CARGO_CFG_TARGET_OS", "testos");
std::env::set_var("CARGO_CFG_TARGET_FAMILY", "testfamily");
std::env::set_var("CARGO_CFG_TARGET_ENV", "testenv");
std::env::set_var("CARGO_CFG_TARGET_ENDIAN", "little");
std::env::set_var("CARGO_CFG_TARGET_POINTER_WIDTH", "32");
std::env::set_var("CARGO_CFG_TARGET_VENDOR", "testvendor");
}
/// No recursion. Check all of the supported tests.
#[test]
fn test_level0() {
setup_test_env();
assert!(target_cfg!(target_arch = "testarch"));
assert!(!target_cfg!(target_arch = "wrong"));
assert!(target_cfg!(target_os = "testos"));
assert!(target_cfg!(target_family = "testfamily"));
assert!(target_cfg!(target_env = "testenv"));
assert!(target_cfg!(target_endian = "little"));
assert!(target_cfg!(target_pointer_width = "32"));
assert!(target_cfg!(target_vendor = "testvendor"));
}
/// Basic recursion.
#[test]
fn test_level1() {
setup_test_env();
assert!(target_cfg!(not(target_arch = "wrong")));
assert!(!target_cfg!(not(target_arch = "testarch")));
assert!(target_cfg!(all(target_arch = "testarch")));
assert!(!target_cfg!(all(target_arch = "wrong")));
assert!(target_cfg!(all(
target_arch = "testarch",
target_os = "testos"
)));
assert!(!target_cfg!(all(
target_arch = "testarch",
target_os = "wrong"
)));
assert!(target_cfg!(any(target_arch = "testarch")));
assert!(!target_cfg!(any(target_arch = "wrong")));
assert!(target_cfg!(any(
target_arch = "testarch",
target_os = "testos"
)));
assert!(target_cfg!(any(
target_arch = "testarch",
target_os = "wrong"
)));
assert!(!target_cfg!(any(
target_arch = "wrong1",
target_os = "wrong2"
)));
}
/// Even deeper recursion.
#[test]
fn test_level2() {
setup_test_env();
assert!(target_cfg!(all(not(target_arch = "wrong"))));
assert!(!target_cfg!(all(not(target_arch = "testarch"))));
assert!(target_cfg!(all(
target_arch = "testarch",
not(target_os = "wrong")
)));
assert!(target_cfg!(all(
any(target_arch = "testarch", target_os = "wrong"),
target_env = "testenv"
)));
assert!(target_cfg!(all(
any(target_arch = "testarch", target_os = "wrong"),
not(target_vendor = "wrong")
)));
}
}