1use crate::{Config, DataModel, LinkType, Threading};
2use anyhow::{bail, Context, Result};
3use std::{
4 fs,
5 io::{self, BufRead},
6 path::{Path, PathBuf},
7 process::Command,
8};
9
10pub fn mkl_libs(cfg: Config) -> Vec<String> {
13 let mut libs = Vec::new();
14 match cfg.index_size {
15 DataModel::LP64 => {
16 libs.push("mkl_intel_lp64".into());
17 }
18 DataModel::ILP64 => {
19 libs.push("mkl_intel_ilp64".into());
20 }
21 };
22 match cfg.parallel {
23 Threading::OpenMP => {
24 libs.push("mkl_intel_thread".into());
25 }
26 Threading::Sequential => {
27 libs.push("mkl_sequential".into());
28 }
29 };
30 libs.push("mkl_core".into());
31
32 if cfg!(target_os = "windows") && cfg.link == LinkType::Dynamic {
33 libs.into_iter().map(|lib| format!("{}_dll", lib)).collect()
34 } else {
35 libs
36 }
37}
38
39pub fn mkl_dyn_libs(cfg: Config) -> Vec<String> {
41 match cfg.link {
42 LinkType::Static => Vec::new(),
43 LinkType::Dynamic => {
44 let mut libs = Vec::new();
45 for prefix in &["mkl", "mkl_vml"] {
46 for suffix in &["def", "avx", "avx2", "avx512", "avx512_mic", "mc", "mc3"] {
47 libs.push(format!("{}_{}", prefix, suffix));
48 }
49 }
50 libs.push("mkl_rt".into());
51 libs.push("mkl_vml_mc2".into());
52 libs.push("mkl_vml_cmpt".into());
53
54 if cfg!(target_os = "windows") {
55 libs.into_iter().map(|lib| format!("{}_dll", lib)).collect()
56 } else {
57 libs
58 }
59 }
60 }
61}
62
63pub fn mkl_file_name(link: LinkType, name: &str) -> String {
65 if cfg!(target_os = "windows") {
66 format!("{}.lib", name)
73 } else {
74 match link {
75 LinkType::Static => {
76 format!("lib{}.a", name)
77 }
78 LinkType::Dynamic => {
79 format!("lib{}.{}", name, std::env::consts::DLL_EXTENSION)
80 }
81 }
82 }
83}
84
85pub const OPENMP_RUNTIME_LIB: &str = if cfg!(target_os = "windows") {
86 "libiomp5md"
87} else {
88 "iomp5"
89};
90
91pub fn openmp_runtime_file_name(link: LinkType) -> String {
93 let name = OPENMP_RUNTIME_LIB;
94 if cfg!(target_os = "windows") {
95 match link {
96 LinkType::Static => {
97 format!("{}.lib", name)
98 }
99 LinkType::Dynamic => {
100 format!("{}.dll", name)
101 }
102 }
103 } else {
104 match link {
105 LinkType::Static => {
106 format!("lib{}.a", name)
107 }
108 LinkType::Dynamic => {
109 format!("lib{}.{}", name, std::env::consts::DLL_EXTENSION)
110 }
111 }
112 }
113}
114
115pub const STATIC_EXTENSION: &str = if cfg!(any(target_os = "linux", target_os = "macos")) {
117 "a"
118} else {
119 "lib"
120};
121
122#[derive(Debug, Clone)]
134pub struct Library {
135 pub config: Config,
136 pub include_dir: PathBuf,
138 pub library_dir: PathBuf,
140
141 pub iomp5_static_dir: Option<PathBuf>,
146
147 pub iomp5_dynamic_dir: Option<PathBuf>,
152}
153
154impl Library {
155 pub fn pkg_config(config: Config) -> Result<Option<Self>> {
174 if let Ok(out) = Command::new("pkg-config")
175 .arg("--variable=prefix")
176 .arg(config.to_string())
177 .output()
178 {
179 if out.status.success() {
180 let path = String::from_utf8(out.stdout).context("Non-UTF8 MKL prefix")?;
181 let prefix = Path::new(path.trim());
182 let prefix = fs::canonicalize(prefix)?;
183 log::info!("pkg-config found {} on {}", config, prefix.display());
184 Self::seek_directory(config, prefix)
185 } else {
186 log::info!("pkg-config does not find {}", config);
187 Ok(None)
188 }
189 } else {
190 log::info!("pkg-config itself is not found");
191 Ok(None)
192 }
193 }
194
195 pub fn seek_directory(config: Config, root_dir: impl AsRef<Path>) -> Result<Option<Self>> {
204 let root_dir = root_dir.as_ref();
205 if !root_dir.is_dir() {
206 return Ok(None);
207 }
208 let mut library_dir = None;
209 let mut include_dir = None;
210 let mut iomp5_static_dir = None;
211 let mut iomp5_dynamic_dir = None;
212 for (dir, file_name) in walkdir::WalkDir::new(root_dir)
213 .into_iter()
214 .flatten() .flat_map(|entry| {
216 let path = entry.into_path();
217 if path.is_dir() {
219 return None;
220 }
221 if path.components().any(|c| {
223 if let std::path::Component::Normal(c) = c {
224 if let Some(c) = c.to_str() {
225 if c.starts_with("ia32") || c == "win-x86" {
226 return true;
227 }
228 }
229 }
230 false
231 }) {
232 return None;
233 }
234
235 let dir = path
236 .parent()
237 .expect("parent must exist here since this is under `root_dir`")
238 .to_owned();
239
240 if let Some(Some(file_name)) = path.file_name().map(|f| f.to_str()) {
241 Some((dir, file_name.to_string()))
242 } else {
243 None
244 }
245 })
246 {
247 if include_dir.is_none() && file_name == "mkl.h" {
248 log::info!("Found mkl.h at {}", dir.display());
249 include_dir = Some(dir);
250 continue;
251 }
252
253 if library_dir.is_none() {
254 for name in mkl_libs(config) {
255 if file_name == mkl_file_name(config.link, &name) {
256 log::info!("Found {} at {}", file_name, dir.display());
257 library_dir = Some(dir.clone());
258 continue;
259 }
260 }
261 }
262
263 if config.parallel == Threading::OpenMP {
265 let possible_link_types = if cfg!(feature = "openmp-strict-link-type") {
269 vec![config.link]
270 } else {
271 vec![config.link, config.link.otherwise()]
272 };
273 for link in possible_link_types {
274 if file_name == openmp_runtime_file_name(link) {
275 match link {
276 LinkType::Static => {
277 log::info!(
278 "Found static OpenMP runtime ({}): {}",
279 file_name,
280 dir.display()
281 );
282 iomp5_static_dir = Some(dir.clone())
283 }
284 LinkType::Dynamic => {
285 log::info!(
286 "Found dynamic OpenMP runtime ({}): {}",
287 file_name,
288 dir.display()
289 );
290 iomp5_dynamic_dir = Some(dir.clone())
291 }
292 }
293 }
294 }
295 }
296 }
297 if config.parallel == Threading::OpenMP
298 && iomp5_dynamic_dir.is_none()
299 && iomp5_static_dir.is_none()
300 {
301 if let Some(ref lib) = library_dir {
302 log::warn!(
303 "OpenMP runtime not found while MKL found at {}",
304 lib.display()
305 );
306 }
307 return Ok(None);
308 }
309 Ok(match (library_dir, include_dir) {
310 (Some(library_dir), Some(include_dir)) => Some(Library {
311 config,
312 include_dir,
313 library_dir,
314 iomp5_static_dir,
315 iomp5_dynamic_dir,
316 }),
317 _ => None,
318 })
319 }
320
321 pub fn new(config: Config) -> Result<Self> {
332 if let Some(lib) = Self::pkg_config(config)? {
333 return Ok(lib);
334 }
335 if let Ok(mklroot) = std::env::var("MKLROOT") {
336 log::info!("MKLROOT environment variable is detected: {}", mklroot);
337 if let Some(lib) = Self::seek_directory(config, mklroot)? {
338 return Ok(lib);
339 }
340 }
341 for path in [
342 "/opt/intel",
343 "C:/Program Files (x86)/IntelSWTools/",
344 "C:/Program Files (x86)/Intel/oneAPI/",
345 ] {
346 let path = Path::new(path);
347 if let Some(lib) = Self::seek_directory(config, path)? {
348 return Ok(lib);
349 }
350 }
351 bail!("Intel MKL not found in system");
352 }
353
354 pub fn available() -> Vec<Self> {
355 Config::possibles()
356 .into_iter()
357 .flat_map(|cfg| Self::new(cfg).ok())
358 .collect()
359 }
360
361 pub fn version(&self) -> Result<(u32, u32, u32)> {
374 let version_h = self.include_dir.join("mkl_version.h");
375
376 let f = fs::File::open(version_h).context("Failed to open mkl_version.h")?;
377 let f = io::BufReader::new(f);
378 let mut year = None;
379 let mut minor = None;
380 let mut update = None;
381 for line in f.lines().flatten() {
382 if !line.starts_with("#define") {
383 continue;
384 }
385 let ss: Vec<&str> = line.split_whitespace().collect();
386 match ss[1] {
387 "__INTEL_MKL__" => year = Some(ss[2].parse()?),
388 "__INTEL_MKL_MINOR__" => minor = Some(ss[2].parse()?),
389 "__INTEL_MKL_UPDATE__" => update = Some(ss[2].parse()?),
390 _ => continue,
391 }
392 }
393 match (year, minor, update) {
394 (Some(year), Some(minor), Some(update)) => Ok((year, minor, update)),
395 _ => bail!("Invalid mkl_version.h"),
396 }
397 }
398
399 pub fn print_cargo_metadata(&self) -> Result<()> {
401 println!("cargo:rerun-if-env-changed=MKLROOT");
402 println!("cargo:rustc-link-search={}", self.library_dir.display());
403 for lib in mkl_libs(self.config) {
404 match self.config.link {
405 LinkType::Static => {
406 println!("cargo:rustc-link-lib=static={}", lib);
407 }
408 LinkType::Dynamic => {
409 println!("cargo:rustc-link-lib=dylib={}", lib);
410 }
411 }
412 }
413
414 if self.config.parallel == Threading::OpenMP {
415 if let Some(ref dir) = self.iomp5_static_dir {
416 println!("cargo:rustc-link-search={}", dir.display());
417 }
418 if let Some(ref dir) = self.iomp5_dynamic_dir {
419 println!("cargo:rustc-link-search={}", dir.display());
420 }
421 println!("cargo:rustc-link-lib={}", OPENMP_RUNTIME_LIB);
422 }
423 Ok(())
424 }
425}