diff options
| author | Steven Fackler <[email protected]> | 2016-11-05 20:06:50 -0700 |
|---|---|---|
| committer | Steven Fackler <[email protected]> | 2016-11-05 20:06:50 -0700 |
| commit | a0b56c437803a08413755928040a0970a93a7b83 (patch) | |
| tree | 0f21848301b62d6078eafaee10e513df4163087b /openssl-sys/build.rs | |
| parent | Merge branch 'release-v0.8.3' into release (diff) | |
| parent | Release v0.9.0 (diff) | |
| download | rust-openssl-0.9.0.tar.xz rust-openssl-0.9.0.zip | |
Merge branch 'release-v0.9.0' into releasev0.9.0
Diffstat (limited to 'openssl-sys/build.rs')
| -rw-r--r-- | openssl-sys/build.rs | 377 |
1 files changed, 318 insertions, 59 deletions
diff --git a/openssl-sys/build.rs b/openssl-sys/build.rs index 0e3a76d2..a435b192 100644 --- a/openssl-sys/build.rs +++ b/openssl-sys/build.rs @@ -1,86 +1,345 @@ extern crate pkg_config; +use std::collections::HashSet; use std::env; +use std::ffi::OsString; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; fn main() { let target = env::var("TARGET").unwrap(); - // libressl_pnacl_sys links the libs needed. - if target.ends_with("nacl") { return; } + let openssl_dir = env::var_os("OPENSSL_DIR").unwrap_or_else(|| { + find_openssl_dir(&target) + }); - let lib_dir = env::var("OPENSSL_LIB_DIR").ok(); - let include_dir = env::var("OPENSSL_INCLUDE_DIR").ok(); + let lib_dir = Path::new(&openssl_dir).join("lib"); + let include_dir = Path::new(&openssl_dir).join("include"); + if !Path::new(&lib_dir).exists() { + panic!("OpenSSL library directory does not exist: {}", + lib_dir.to_string_lossy()); + } - if lib_dir.is_none() && include_dir.is_none() { - // rustc doesn't seem to work with pkg-config's output in mingw64 - if !target.contains("windows") { - if let Ok(info) = pkg_config::find_library("openssl") { - // avoid empty include paths as they are not supported by GCC - if info.include_paths.len() > 0 { - let paths = env::join_paths(info.include_paths).unwrap(); - println!("cargo:include={}", paths.to_str().unwrap()); - } - return; - } + if !Path::new(&include_dir).exists() { + panic!("OpenSSL include directory does not exist: {}", + include_dir.to_string_lossy()); + } + + println!("cargo:rustc-link-search=native={}", lib_dir.to_string_lossy()); + println!("cargo:include={}", include_dir.to_string_lossy()); + + let version = validate_headers(&[include_dir.clone().into()], + &[lib_dir.clone().into()]); + + let libs = if (version.contains("0x10001") || + version.contains("0x10002")) && + target.contains("windows") { + ["ssleay32", "libeay32"] + } else if target.contains("windows") { + ["libssl", "libcrypto"] + } else { + ["ssl", "crypto"] + }; + + let kind = determine_mode(Path::new(&lib_dir), &libs); + for lib in libs.iter() { + println!("cargo:rustc-link-lib={}={}", kind, lib); + } +} + +fn find_openssl_dir(target: &str) -> OsString { + let host = env::var("HOST").unwrap(); + + if host.contains("apple-darwin") && target.contains("apple-darwin") { + let homebrew = Path::new("/usr/local/opt/[email protected]"); + if homebrew.exists() { + return homebrew.to_path_buf().into() } - if let Some(mingw_paths) = get_mingw_in_path() { - for path in mingw_paths { - println!("cargo:rustc-link-search=native={}", path); - } + let homebrew = Path::new("/usr/local/opt/openssl"); + if homebrew.exists() { + return homebrew.to_path_buf().into() } } - let libs_env = env::var("OPENSSL_LIBS").ok(); - let libs = match libs_env { - Some(ref v) => v.split(":").collect(), - None => if target.contains("windows") { - if get_mingw_in_path().is_some() && lib_dir.is_none() && include_dir.is_none() { - vec!["ssleay32", "eay32"] - } else { - vec!["ssl32", "eay32"] - } - } else { - vec!["ssl", "crypto"] + try_pkg_config(); + + let mut msg = format!(" + +Could not find directory of OpenSSL installation, and this `-sys` crate cannot +proceed without this knowledge. If OpenSSL is installed and this crate had +trouble finding it, you can set the `OPENSSL_DIR` environment variable for the +compilation process. + +If you're in a situation where you think the directory *should* be found +automatically, please open a bug at https://github.com/sfackler/rust-openssl +and include information about your system as well as this message. + + $HOST = {} + $TARGET = {} + openssl-sys = {} + +", + host, target, env!("CARGO_PKG_VERSION")); + + if host.contains("apple-darwin") && target.contains("apple-darwin") { + let system = Path::new("/usr/lib/libssl.0.9.8.dylib"); + if system.exists() { + msg.push_str(&format!(" + +It looks like you're compiling on macOS, where the system contains a version of +OpenSSL 0.9.8. This crate no longer supports OpenSSL 0.9.8. + +As a consumer of this crate, you can fix this error by using Homebrew to +install the `openssl` package, or as a maintainer you can use the openssl-sys +0.7 crate for support with OpenSSL 0.9.8. + +Unfortunately though the compile cannot continue, so aborting. + +")); } - }; + } - let mode = if env::var_os("OPENSSL_STATIC").is_some() { - "static" - } else { - "dylib" - }; + if host.contains("windows") && target.contains("windows-gnu") { + msg.push_str(&format!(" +It looks like you're compiling for MinGW but you may not have either OpenSSL or +pkg-config installed. You can install these two dependencies with: + + pacman -S openssl pkg-config + +and try building this crate again. + +" +)); + } + + if host.contains("windows") && target.contains("windows-msvc") { + msg.push_str(&format!(" +It looks like you're compiling for MSVC but we couldn't detect an OpenSSL +installation. If there isn't one installed then you can try the rust-openssl +README for more information about how to download precompiled binaries of +OpenSSL: + + https://github.com/sfackler/rust-openssl#windows + +" +)); + } + + panic!(msg); +} + +/// Attempt to find OpenSSL through pkg-config. +/// +/// Note that if this succeeds then the function does not return as pkg-config +/// typically tells us all the information that we need. +fn try_pkg_config() { + let target = env::var("TARGET").unwrap(); + let host = env::var("HOST").unwrap(); - if let Some(lib_dir) = lib_dir { - println!("cargo:rustc-link-search=native={}", lib_dir); + // If we're going to windows-gnu we can use pkg-config, but only so long as + // we're coming from a windows host. + // + // Otherwise if we're going to windows we probably can't use pkg-config. + if target.contains("windows-gnu") && host.contains("windows") { + env::set_var("PKG_CONFIG_ALLOW_CROSS", "1"); + } else if target.contains("windows") { + return } - for lib in libs { - println!("cargo:rustc-link-lib={}={}", mode, lib); + // We're going to be looking at header files, so show us all the system + // cflags dirs for showing us lots of `-I`. + env::set_var("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", "1"); + + let lib = match pkg_config::find_library("openssl") { + Ok(lib) => lib, + Err(_) => return, + }; + + if lib.include_paths.len() == 0 { + panic!(" + +Used pkg-config to discover the OpenSSL installation, but pkg-config did not +return any include paths for the installation. This crate needs to take a peek +at the header files so it cannot proceed unless they're found. + +You can try fixing this by setting the `OPENSSL_DIR` environment variable +pointing to your OpenSSL installation. + +"); } - if let Some(include_dir) = include_dir { - println!("cargo:include={}", include_dir); + validate_headers(&lib.include_paths, &lib.link_paths); + + for include in lib.include_paths.iter() { + println!("cargo:include={}", include.display()); } + + std::process::exit(0); } -fn get_mingw_in_path() -> Option<Vec<String>> { - match env::var_os("PATH") { - Some(env_path) => { - let paths: Vec<String> = env::split_paths(&env_path).filter_map(|path| { - use std::ascii::AsciiExt; - - match path.to_str() { - Some(path_str) => { - if path_str.to_ascii_lowercase().contains("mingw") { - Some(path_str.to_string()) - } else { None } - }, - None => None +/// Validates the header files found in `include_dir` and then returns the +/// version string of OpenSSL. +fn validate_headers(include_dirs: &[PathBuf], + libdirs: &[PathBuf]) -> String { + // 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 + // `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 { + 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 + // 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. + // + // To handle all this conditional compilation we slurp up the configuration + // file of OpenSSL, `opensslconf.h`, and then dump out everything it defines + // as our own #[cfg] directives. That way the `ossl10x.rs` bindings can + // account for compile differences and such. + let mut conf_header = String::new(); + let mut include = include_dirs.iter() + .map(|p| p.join("openssl/opensslconf.h")) + .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); } - }).collect(); + } + } + }; + f.read_to_string(&mut conf_header).unwrap(); - if paths.len() > 0 { Some(paths) } else { None } - }, - None => None + // Look for `#define OPENSSL_FOO`, print out everything as our own + // #[cfg] flag. + let mut vars = vec![]; + 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() +} + +/// Given a libdir for OpenSSL (where artifacts are located) as well as the name +/// of the libraries we're linking to, figure out whether we should link them +/// statically or dynamically. +fn determine_mode(libdir: &Path, libs: &[&str]) -> &'static str { + // First see if a mode was explicitly requested + let kind = env::var("OPENSSL_STATIC").ok(); + match kind.as_ref().map(|s| &s[..]) { + Some("0") => return "dylib", + Some(_) => return "static", + None => {} } + + // Next, see what files we actually have to link against, and see what our + // possibilities even are. + let files = libdir.read_dir().unwrap() + .map(|e| e.unwrap()) + .map(|e| e.file_name()) + .filter_map(|e| e.into_string().ok()) + .collect::<HashSet<_>>(); + let can_static = libs.iter().all(|l| { + files.contains(&format!("lib{}.a", l)) || + files.contains(&format!("{}.lib", l)) + }); + let can_dylib = libs.iter().all(|l| { + files.contains(&format!("lib{}.so", l)) || + files.contains(&format!("{}.dll", l)) || + files.contains(&format!("lib{}.dylib", l)) + }); + match (can_static, can_dylib) { + (true, false) => return "static", + (false, true) => return "dylib", + (false, false) => { + panic!("OpenSSL libdir at `{}` does not contain the required files \ + to either statically or dynamically link OpenSSL", + libdir.display()); + } + (true, true) => {} + } + + // Ok, we've got not explicit preference and can *either* link statically or + // link dynamically. In the interest of "security upgrades" and/or "best + // practices with security libs", let's link dynamically. + "dylib" } |