Macro-expand OpenSSL headers for feature checks

Closes #564
This commit is contained in:
Steven Fackler 2017-01-28 17:49:41 -08:00
parent 3f4a78f06f
commit 0598561f0e
2 changed files with 109 additions and 124 deletions

View File

@ -16,11 +16,12 @@ libc = "0.2"
[build-dependencies] [build-dependencies]
pkg-config = "0.3.9" pkg-config = "0.3.9"
gcc = "0.3.42"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
user32-sys = "0.2" user32-sys = "0.2"
gdi32-sys = "0.2" gdi32-sys = "0.2"
# We don't actually use metadeps for annoying reasons but this is still hear for tooling # We don't actually use metadeps for annoying reasons but this is still here for tooling
[package.metadata.pkg-config] [package.metadata.pkg-config]
openssl = "1.0.1" openssl = "1.0.1"

View File

@ -1,13 +1,37 @@
extern crate pkg_config; extern crate pkg_config;
extern crate gcc;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
// The set of `OPENSSL_NO_<FOO>`s that we care about.
const DEFINES: &'static [&'static str] = &[
"OPENSSL_NO_BUF_FREELISTS",
"OPENSSL_NO_COMP",
"OPENSSL_NO_EC",
"OPENSSL_NO_ENGINE",
"OPENSSL_NO_KRB5",
"OPENSSL_NO_NEXTPROTONEG",
"OPENSSL_NO_PSK",
"OPENSSL_NO_RFC3779",
"OPENSSL_NO_SHA",
"OPENSSL_NO_SRP",
"OPENSSL_NO_SSL3_METHOD",
"OPENSSL_NO_TLSEXT",
];
enum Version {
Openssl110,
Openssl102,
Openssl101,
Libressl,
}
fn main() { fn main() {
let target = env::var("TARGET").unwrap(); let target = env::var("TARGET").unwrap();
@ -38,17 +62,14 @@ fn main() {
println!("cargo:rustc-link-search=native={}", lib_dir.to_string_lossy()); println!("cargo:rustc-link-search=native={}", lib_dir.to_string_lossy());
println!("cargo:include={}", include_dir.to_string_lossy()); println!("cargo:include={}", include_dir.to_string_lossy());
let version = validate_headers(&[include_dir.clone().into()], let version = validate_headers(&[include_dir.clone().into()]);
&[lib_dir.clone().into()]);
let libs = if (version.contains("0x10001") || let libs = match version {
version.contains("0x10002")) && Version::Openssl101 | Version::Openssl102 if target.contains("windows") => {
target.contains("windows") {
["ssleay32", "libeay32"] ["ssleay32", "libeay32"]
} else if target.contains("windows") { }
["libssl", "libcrypto"] Version::Openssl110 if target.contains("windows") => ["libssl", "libcrypto"],
} else { _ => ["ssl", "crypto"],
["ssl", "crypto"]
}; };
let kind = determine_mode(Path::new(&lib_dir), &libs); let kind = determine_mode(Path::new(&lib_dir), &libs);
@ -209,7 +230,7 @@ See rust-openssl README for more information:
"); ");
} }
validate_headers(&lib.include_paths, &lib.link_paths); validate_headers(&lib.include_paths);
for include in lib.include_paths.iter() { for include in lib.include_paths.iter() {
println!("cargo:include={}", include.display()); println!("cargo:include={}", include.display());
@ -225,74 +246,11 @@ See rust-openssl README for more information:
/// Validates the header files found in `include_dir` and then returns the /// Validates the header files found in `include_dir` and then returns the
/// version string of OpenSSL. /// version string of OpenSSL.
fn validate_headers(include_dirs: &[PathBuf], fn validate_headers(include_dirs: &[PathBuf]) -> Version {
libdirs: &[PathBuf]) -> String {
// This `*-sys` crate only works with OpenSSL 1.0.1, 1.0.2, and 1.1.0. To // This `*-sys` crate only works with OpenSSL 1.0.1, 1.0.2, and 1.1.0. To
// correctly expose the right API from this crate, take a look at // correctly expose the right API from this crate, take a look at
// `opensslv.h` to see what version OpenSSL claims to be. // `opensslv.h` to see what version OpenSSL claims to be.
let mut version_header = String::new(); //
let mut include = include_dirs.iter()
.map(|p| p.join("openssl/opensslv.h"))
.filter(|p| p.exists());
let mut f = match include.next() {
Some(f) => File::open(f).unwrap(),
None => {
panic!("failed to open header file at `openssl/opensslv.h` to learn
about OpenSSL's version number, looked inside:\n\n{:#?}\n\n",
include_dirs);
}
};
f.read_to_string(&mut version_header).unwrap();
// Do a bit of string parsing to find `#define OPENSSL_VERSION_NUMBER ...`
let version_line = version_header.lines().find(|l| {
l.contains("define ") && l.contains("OPENSSL_VERSION_NUMBER")
}).and_then(|line| {
let start = match line.find("0x") {
Some(start) => start,
None => return None,
};
Some(line[start..].trim())
});
let version_text = match version_line {
Some(text) => text,
None => {
panic!("header file at `{}` did not include `OPENSSL_VERSION_NUMBER` \
that this crate recognized, failed to learn about the \
OpenSSL version number");
}
};
if version_text.contains("0x10001") {
println!("cargo:rustc-cfg=ossl101");
println!("cargo:version=101");
} else if version_text.contains("0x10002") {
println!("cargo:rustc-cfg=ossl102");
println!("cargo:version=102");
} else if version_text.contains("0x10100") {
println!("cargo:rustc-cfg=ossl110");
println!("cargo:version=110");
} else if version_text.contains("0x20000000L") {
// Check if it is really LibreSSL
if version_header.lines().any(|l| {
l.contains("define ") && l.contains("LIBRESSL_VERSION_NUMBER")
}) {
println!("cargo:rustc-cfg=libressl");
println!("cargo:libressl=true");
println!("cargo:version=101");
}
} else {
panic!("
This crate is only compatible with OpenSSL 1.0.1, 1.0.2, and 1.1.0, but a
different version of OpenSSL was found:
{}
The build is now aborting due to this version mismatch.
", version_text);
}
// OpenSSL has a number of build-time configuration options which affect // OpenSSL has a number of build-time configuration options which affect
// various structs and such. Since OpenSSL 1.1.0 this isn't really a problem // various structs and such. Since OpenSSL 1.1.0 this isn't really a problem
// as the library is much more FFI-friendly, but 1.0.{1,2} suffer this problem. // as the library is much more FFI-friendly, but 1.0.{1,2} suffer this problem.
@ -301,56 +259,82 @@ The build is now aborting due to this version mismatch.
// file of OpenSSL, `opensslconf.h`, and then dump out everything it defines // file of OpenSSL, `opensslconf.h`, and then dump out everything it defines
// as our own #[cfg] directives. That way the `ossl10x.rs` bindings can // as our own #[cfg] directives. That way the `ossl10x.rs` bindings can
// account for compile differences and such. // account for compile differences and such.
let mut conf_header = String::new(); let mut path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let mut include = include_dirs.iter() path.push("expando.c");
.map(|p| p.join("openssl/opensslconf.h")) let mut file = BufWriter::new(File::create(&path).unwrap());
.filter(|p| p.exists());
let mut f = match include.next() {
Some(f) => File::open(f).unwrap(),
None => {
// It's been seen that on linux the include dir printed out by
// `pkg-config` doesn't actually have opensslconf.h. Instead
// it's in an architecture-specific include directory.
//
// Try to detect that case to see if it exists.
let mut libdirs = libdirs.iter().map(|p| {
p.iter()
.map(|p| if p == "lib" {"include".as_ref()} else {p})
.collect::<PathBuf>()
}).map(|p| {
p.join("openssl/opensslconf.h")
}).filter(|p| p.exists());
match libdirs.next() {
Some(f) => File::open(f).unwrap(),
None => {
panic!("failed to open header file at
`openssl/opensslconf.h` to learn about \
OpenSSL's version number, looked \
inside:\n\n{:#?}\n\n",
include_dirs);
}
}
}
};
f.read_to_string(&mut conf_header).unwrap();
// Look for `#define OPENSSL_FOO`, print out everything as our own write!(file, "\
// #[cfg] flag. #include <openssl/opensslv.h>
let mut vars = vec![]; #include <openssl/opensslconf.h>
for line in conf_header.lines() {
let i = match line.find("define ") {
Some(i) => i,
None => continue,
};
let var = line[i + "define ".len()..].trim();
if var.starts_with("OPENSSL") && !var.contains(" ") {
println!("cargo:rustc-cfg=osslconf=\"{}\"", var);
vars.push(var);
}
}
println!("cargo:conf={}", vars.join(","));
return version_text.to_string() #ifdef LIBRESSL_VERSION_NUMBER
RUST_LIBRESSL
#elif OPENSSL_VERSION_NUMBER >= 0x10200000
RUST_OPENSSL_NEW
#elif OPENSSL_VERSION_NUMBER >= 0x10100000
RUST_OPENSSL_110
#elif OPENSSL_VERSION_NUMBER >= 0x10002000
RUST_OPENSSL_102
#elif OPENSSL_VERSION_NUMBER >= 0x10001000
RUST_OPENSSL_101
#else
RUST_OPENSSL_OLD
#endif
").unwrap();
for define in DEFINES {
write!(file, "\
#ifdef {define}
RUST_{define}
#endif
", define = define).unwrap();
}
file.flush().unwrap();
drop(file);
let mut gcc = gcc::Config::new();
for include_dir in include_dirs {
gcc.include(include_dir);
}
let expanded = gcc.file(&path).expand();
let expanded = String::from_utf8(expanded).unwrap();
let mut enabled = vec![];
for &define in DEFINES {
if expanded.contains(&format!("RUST_{}", define)) {
println!("cargo:rustc-cfg=osslconf=\"{}\"", define);
enabled.push(define);
}
}
println!("cargo:conf={}", enabled.join(","));
if expanded.contains("RUST_LIBRESSL") {
println!("cargo:rustc-cfg=libressl");
println!("cargo:libressl=true");
println!("cargo:version=101");
Version::Libressl
} else if expanded.contains("RUST_OPENSSL_110") {
println!("cargo:rustc-cfg=ossl110");
println!("cargo:version=110");
Version::Openssl110
} else if expanded.contains("RUST_OPENSSL_102") {
println!("cargo:rustc-cfg=ossl102");
println!("cargo:version=102");
Version::Openssl102
} else if expanded.contains("RUST_OPENSSL_101") {
println!("cargo:rustc-cfg=ossl101");
println!("cargo:version=101");
Version::Openssl101
} else {
panic!("
This crate is only compatible with OpenSSL 1.0.1, 1.0.2, and 1.1.0, or LibreSSL,
but a different version of OpenSSL was found. The build is now aborting due to
this version mismatch.
");
}
} }
/// Given a libdir for OpenSSL (where artifacts are located) as well as the name /// Given a libdir for OpenSSL (where artifacts are located) as well as the name