• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::ffi::OsString;
2 use std::path::{Path, PathBuf};
3 use std::process::{self, Command};
4 
5 use super::env;
6 
get_openssl(target: &str) -> (Vec<PathBuf>, PathBuf)7 pub fn get_openssl(target: &str) -> (Vec<PathBuf>, PathBuf) {
8     let lib_dir = env("OPENSSL_LIB_DIR").map(PathBuf::from);
9     let include_dir = env("OPENSSL_INCLUDE_DIR").map(PathBuf::from);
10 
11     match (lib_dir, include_dir) {
12         (Some(lib_dir), Some(include_dir)) => (vec![lib_dir], include_dir),
13         (lib_dir, include_dir) => {
14             let openssl_dir = env("OPENSSL_DIR").unwrap_or_else(|| find_openssl_dir(target));
15             let openssl_dir = Path::new(&openssl_dir);
16             let lib_dir = lib_dir.map(|d| vec![d]).unwrap_or_else(|| {
17                 let mut lib_dirs = vec![];
18                 // OpenSSL 3.0 now puts it's libraries in lib64/ by default,
19                 // check for both it and lib/.
20                 if openssl_dir.join("lib64").exists() {
21                     lib_dirs.push(openssl_dir.join("lib64"));
22                 }
23                 if openssl_dir.join("lib").exists() {
24                     lib_dirs.push(openssl_dir.join("lib"));
25                 }
26                 lib_dirs
27             });
28             let include_dir = include_dir.unwrap_or_else(|| openssl_dir.join("include"));
29             (lib_dir, include_dir)
30         }
31     }
32 }
33 
resolve_with_wellknown_homebrew_location(dir: &str) -> Option<PathBuf>34 fn resolve_with_wellknown_homebrew_location(dir: &str) -> Option<PathBuf> {
35     let versions = ["openssl@3", "openssl@1.1"];
36 
37     // Check up default aarch 64 Homebrew installation location first
38     // for quick resolution if possible.
39     //  `pkg-config` on brew doesn't necessarily contain settings for openssl apparently.
40     for version in &versions {
41         let homebrew = Path::new(dir).join(format!("opt/{}", version));
42         if homebrew.exists() {
43             return Some(homebrew);
44         }
45     }
46 
47     for version in &versions {
48         // Calling `brew --prefix <package>` command usually slow and
49         // takes seconds, and will be used only as a last resort.
50         let output = execute_command_and_get_output("brew", &["--prefix", version]);
51         if let Some(ref output) = output {
52             let homebrew = Path::new(&output);
53             if homebrew.exists() {
54                 return Some(homebrew.to_path_buf());
55             }
56         }
57     }
58 
59     None
60 }
61 
resolve_with_wellknown_location(dir: &str) -> Option<PathBuf>62 fn resolve_with_wellknown_location(dir: &str) -> Option<PathBuf> {
63     let root_dir = Path::new(dir);
64     let include_openssl = root_dir.join("include/openssl");
65     if include_openssl.exists() {
66         Some(root_dir.to_path_buf())
67     } else {
68         None
69     }
70 }
71 
find_openssl_dir(target: &str) -> OsString72 fn find_openssl_dir(target: &str) -> OsString {
73     let host = env::var("HOST").unwrap();
74 
75     if host == target && target.ends_with("-apple-darwin") {
76         let homebrew_dir = match target {
77             "aarch64-apple-darwin" => "/opt/homebrew",
78             _ => "/usr/local",
79         };
80 
81         if let Some(dir) = resolve_with_wellknown_homebrew_location(homebrew_dir) {
82             return dir.into();
83         } else if let Some(dir) = resolve_with_wellknown_location("/opt/pkg") {
84             // pkgsrc
85             return dir.into();
86         } else if let Some(dir) = resolve_with_wellknown_location("/opt/local") {
87             // MacPorts
88             return dir.into();
89         }
90     }
91 
92     try_pkg_config();
93     try_vcpkg();
94 
95     // FreeBSD ships with OpenSSL but doesn't include a pkg-config file :(
96     if host == target && target.contains("freebsd") {
97         return OsString::from("/usr");
98     }
99 
100     // DragonFly has libressl (or openssl) in ports, but this doesn't include a pkg-config file
101     if host == target && target.contains("dragonfly") {
102         return OsString::from("/usr/local");
103     }
104 
105     let mut msg = format!(
106         "
107 
108 Could not find directory of OpenSSL installation, and this `-sys` crate cannot
109 proceed without this knowledge. If OpenSSL is installed and this crate had
110 trouble finding it,  you can set the `OPENSSL_DIR` environment variable for the
111 compilation process.
112 
113 Make sure you also have the development packages of openssl installed.
114 For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
115 
116 If you're in a situation where you think the directory *should* be found
117 automatically, please open a bug at https://github.com/sfackler/rust-openssl
118 and include information about your system as well as this message.
119 
120 $HOST = {}
121 $TARGET = {}
122 openssl-sys = {}
123 
124 ",
125         host,
126         target,
127         env!("CARGO_PKG_VERSION")
128     );
129 
130     if host.contains("apple-darwin") && target.contains("apple-darwin") {
131         let system = Path::new("/usr/lib/libssl.0.9.8.dylib");
132         if system.exists() {
133             msg.push_str(
134                 "
135 
136 openssl-sys crate build failed: no supported version of OpenSSL found.
137 
138 Ways to fix it:
139 - Use the `vendored` feature of openssl-sys crate to build OpenSSL from source.
140 - Use Homebrew to install the `openssl` package.
141 
142 ",
143             );
144         }
145     }
146 
147     if host.contains("unknown-linux")
148         && target.contains("unknown-linux-gnu")
149         && Command::new("pkg-config").output().is_err()
150     {
151         msg.push_str(
152             "
153 It looks like you're compiling on Linux and also targeting Linux. Currently this
154 requires the `pkg-config` utility to find OpenSSL but unfortunately `pkg-config`
155 could not be found. If you have OpenSSL installed you can likely fix this by
156 installing `pkg-config`.
157 
158 ",
159         );
160     }
161 
162     if host.contains("windows") && target.contains("windows-gnu") {
163         msg.push_str(
164             "
165 It looks like you're compiling for MinGW but you may not have either OpenSSL or
166 pkg-config installed. You can install these two dependencies with:
167 
168 pacman -S openssl-devel pkg-config
169 
170 and try building this crate again.
171 
172 ",
173         );
174     }
175 
176     if host.contains("windows") && target.contains("windows-msvc") {
177         msg.push_str(
178             "
179 It looks like you're compiling for MSVC but we couldn't detect an OpenSSL
180 installation. If there isn't one installed then you can try the rust-openssl
181 README for more information about how to download precompiled binaries of
182 OpenSSL:
183 
184 https://github.com/sfackler/rust-openssl#windows
185 
186 ",
187         );
188     }
189 
190     panic!("{}", msg);
191 }
192 
193 /// Attempt to find OpenSSL through pkg-config.
194 ///
195 /// Note that if this succeeds then the function does not return as pkg-config
196 /// typically tells us all the information that we need.
try_pkg_config()197 fn try_pkg_config() {
198     let target = env::var("TARGET").unwrap();
199     let host = env::var("HOST").unwrap();
200 
201     // If we're going to windows-gnu we can use pkg-config, but only so long as
202     // we're coming from a windows host.
203     //
204     // Otherwise if we're going to windows we probably can't use pkg-config.
205     if target.contains("windows-gnu") && host.contains("windows") {
206         env::set_var("PKG_CONFIG_ALLOW_CROSS", "1");
207     } else if target.contains("windows") {
208         return;
209     }
210 
211     let lib = match pkg_config::Config::new()
212         .print_system_libs(false)
213         .probe("openssl")
214     {
215         Ok(lib) => lib,
216         Err(e) => {
217             println!("run pkg_config fail: {:?}", e);
218             return;
219         }
220     };
221 
222     super::postprocess(&lib.include_paths);
223 
224     for include in lib.include_paths.iter() {
225         println!("cargo:include={}", include.display());
226     }
227 
228     process::exit(0);
229 }
230 
231 /// Attempt to find OpenSSL through vcpkg.
232 ///
233 /// Note that if this succeeds then the function does not return as vcpkg
234 /// should emit all of the cargo metadata that we need.
235 #[cfg(target_env = "msvc")]
try_vcpkg()236 fn try_vcpkg() {
237     // vcpkg will not emit any metadata if it can not find libraries
238     // appropriate for the target triple with the desired linkage.
239 
240     let lib = match vcpkg::Config::new()
241         .emit_includes(true)
242         .find_package("openssl")
243     {
244         Ok(lib) => lib,
245         Err(e) => {
246             println!("note: vcpkg did not find openssl: {}", e);
247             return;
248         }
249     };
250 
251     super::postprocess(&lib.include_paths);
252 
253     println!("cargo:rustc-link-lib=user32");
254     println!("cargo:rustc-link-lib=gdi32");
255     println!("cargo:rustc-link-lib=crypt32");
256 
257     process::exit(0);
258 }
259 
260 #[cfg(not(target_env = "msvc"))]
try_vcpkg()261 fn try_vcpkg() {}
262 
execute_command_and_get_output(cmd: &str, args: &[&str]) -> Option<String>263 fn execute_command_and_get_output(cmd: &str, args: &[&str]) -> Option<String> {
264     let out = Command::new(cmd).args(args).output();
265     if let Ok(ref r1) = out {
266         if r1.status.success() {
267             let r2 = String::from_utf8(r1.stdout.clone());
268             if let Ok(r3) = r2 {
269                 return Some(r3.trim().to_string());
270             }
271         }
272     }
273 
274     None
275 }
276