spack/
utils.rs

1/* Copyright 2022-2023 Danny McClanahan */
2/* SPDX-License-Identifier: (Apache-2.0 OR MIT) */
3
4use crate::{
5  commands::{self, find, install},
6  subprocess::spack::SpackInvocation,
7};
8
9use std::{fs, io, path::Path};
10
11/// Like [`fs::create_dir_all`], except handles concurrent calls among multiple
12/// threads or processes. Originally lifted from rustc, then from pants.
13pub fn safe_create_dir_all_ioerror(path: &Path) -> Result<(), io::Error> {
14  match fs::create_dir(path) {
15    Ok(()) => return Ok(()),
16    Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => return Ok(()),
17    Err(ref e) if e.kind() == io::ErrorKind::NotFound => {},
18    Err(e) => return Err(e),
19  }
20  match path.parent() {
21    Some(p) => safe_create_dir_all_ioerror(p)?,
22    None => return Ok(()),
23  }
24  match fs::create_dir(path) {
25    Ok(()) => Ok(()),
26    Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
27    Err(e) => Err(e),
28  }
29}
30
31/// Call `spack install <spec>` and parse the result of `spack find --json`.
32///
33/// The installation via `spack install` will be cached using spack's normal
34/// caching mechanisms.
35/* TODO: is this used outside of the LLVM bootstrap? */
36pub async fn ensure_installed(
37  spack: SpackInvocation,
38  spec: commands::CLISpec,
39) -> Result<find::FoundSpec, crate::Error> {
40  let install = install::Install {
41    spack: spack.clone(),
42    spec,
43    verbosity: Default::default(),
44    env: None,
45    repos: None,
46  };
47  let found_spec = install
48    .clone()
49    .install_find()
50    .await
51    .map_err(|e| commands::CommandError::Install(install, e))?;
52  Ok(found_spec)
53}
54
55/// Call [`ensure_installed`], then return its installation root prefix from
56/// within `opt/spack/...`.
57/* TODO: is this used outside of the LLVM bootstrap? */
58pub async fn ensure_prefix(
59  spack: SpackInvocation,
60  spec: commands::CLISpec,
61) -> Result<prefix::Prefix, crate::Error> {
62  let found_spec = ensure_installed(spack.clone(), spec).await?;
63  let find_prefix = find::FindPrefix {
64    spack,
65    spec: found_spec.hashed_spec(),
66    env: None,
67    repos: None,
68  };
69  let prefix = find_prefix
70    .clone()
71    .find_prefix()
72    .await
73    .map_err(|e| commands::CommandError::FindPrefix(find_prefix, e))?
74    .expect("when using a spec with a hash, FindPrefix should never return None");
75  Ok(prefix)
76}
77
78/// Utilities for building code with [wasm] support via [emscripten].
79///
80/// [wasm]: https://webassembly.org
81/// [emscripten]: https://emscripten.org
82pub mod wasm {
83  use crate::commands::find;
84
85  const LLVM_FOR_WASM: &str = "llvm@14:\
86+lld+clang+multiple-definitions\
87~compiler-rt~tools-extra-clang~libcxx~gold~openmp~internal_unwind~polly \
88targets=webassembly";
89
90  /// Ensure that a version of llvm is installed that is able to support
91  /// emscripten.
92  pub async fn ensure_wasm_ready_llvm(
93    spack: crate::SpackInvocation,
94  ) -> Result<find::FoundSpec, crate::Error> {
95    let llvm_found_spec = super::ensure_installed(spack, LLVM_FOR_WASM.into()).await?;
96    Ok(llvm_found_spec)
97  }
98}
99
100#[cfg(test)]
101mod test {
102  use tokio;
103
104  /* #[tokio::test] */
105  /* async fn test_ensure_wasm_ready_llvm() -> Result<(), crate::Error> { */
106  /* use crate::{utils, SpackInvocation}; */
107  /* use super_process::{exe, fs, sync::SyncInvocable}; */
108
109  /* // Locate all the executables. */
110  /* let spack = SpackInvocation::summon().await?; */
111
112  /* // Let's look for an `llvm` installation, and find the `llvm-config`
113   * executable. */
114  /* let llvm = utils::wasm::ensure_wasm_ready_llvm(spack.clone()).await?; */
115  /* let llvm_prefix = utils::ensure_prefix(spack, llvm.hashed_spec()).await?; */
116  /* let llvm_config_path = llvm_prefix.path.join("bin").join("llvm-config"); */
117
118  /* // Let's make sure the executable can be executed successfully! */
119  /* let command = exe::Command { */
120  /* exe: exe::Exe(fs::File(llvm_config_path)), */
121  /* argv: ["--targets-built"].as_ref().into(), */
122  /* ..Default::default() */
123  /* }; */
124  /* let output = command */
125  /* .invoke() */
126  /* .await */
127  /* .expect("expected llvm-config command to succeed"); */
128  /* let stdout = std::str::from_utf8(&output.stdout).unwrap(); */
129  /* assert!(stdout.contains("WebAssembly")); */
130  /* Ok(()) */
131  /* } */
132
133  #[tokio::test]
134  async fn test_ensure_prefix() -> Result<(), crate::Error> {
135    use crate::{utils, SpackInvocation};
136    use super_process::{exe, fs, sync::SyncInvocable};
137
138    // Locate all the executables.
139    let spack = SpackInvocation::summon().await?;
140
141    // Let's look for a `zip` installation, and find the `zip` executable.
142    let zip_prefix = utils::ensure_prefix(spack, "zip".into()).await?;
143    let zip_bin_path = zip_prefix.path.join("bin").join("zip");
144
145    // Let's make sure the executable can be executed successfully!
146    let command = exe::Command {
147      exe: exe::Exe(fs::File(zip_bin_path)),
148      argv: ["--version"].as_ref().into(),
149      ..Default::default()
150    };
151    let output = command
152      .clone()
153      .invoke()
154      .await
155      .expect("expected zip command to succeed");
156    assert!(output.decode(command).unwrap().stdout.contains("\nThis is Zip "));
157    Ok(())
158  }
159
160  #[tokio::test]
161  async fn test_ensure_installed() -> Result<(), crate::Error> {
162    // Locate all the executables.
163    let spack = crate::SpackInvocation::summon().await?;
164
165    // Let's look for a `zlib` installation.
166    let zlib_spec = crate::utils::ensure_installed(spack, "zlib".into()).await?;
167    assert!(&zlib_spec.name == "zlib");
168    Ok(())
169  }
170}
171
172pub mod prefix {
173  use async_stream::try_stream;
174  use displaydoc::Display;
175  use futures_core::stream::Stream;
176  use futures_util::{pin_mut, stream::TryStreamExt};
177  use indexmap::{IndexMap, IndexSet};
178  use once_cell::sync::Lazy;
179  use regex::Regex;
180  use thiserror::Error;
181  use walkdir;
182
183  use std::path::{Path, PathBuf};
184
185  #[derive(Debug, Display, Error)]
186  pub enum PrefixTraversalError {
187    /// walkdir error: {0}
188    Walkdir(#[from] walkdir::Error),
189    /// needed libs {0:?} not found at prefix {1:?}: found libs were {2:?}
190    NeededLibrariesNotFound(IndexSet<LibraryName>, Prefix, IndexSet<LibraryName>),
191    /// duplicated libs {0:?} found at multiple paths from prefix {1:?}:\n{2:?}
192    DuplicateLibraryNames(
193      IndexSet<LibraryName>,
194      Prefix,
195      IndexMap<LibraryName, Vec<CABILibrary>>,
196    ),
197  }
198
199  /// {0}
200  #[derive(Debug, Display, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
201  pub struct LibraryName(pub String);
202
203  #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
204  pub enum LibraryType {
205    Static,
206    #[default]
207    Dynamic,
208  }
209
210  impl LibraryType {
211    pub(crate) fn cargo_lib_type(&self) -> &'static str {
212      match self {
213        Self::Static => "static",
214        Self::Dynamic => "dylib",
215      }
216    }
217
218    pub(crate) fn match_filename_suffix(suffix: &str) -> Option<Self> {
219      match suffix {
220        "a" => Some(Self::Static),
221        "so" => Some(Self::Dynamic),
222        _ => None,
223      }
224    }
225  }
226
227  #[derive(Debug, Clone)]
228  pub struct CABILibrary {
229    pub name: LibraryName,
230    pub path: PathBuf,
231    pub kind: LibraryType,
232  }
233
234  impl CABILibrary {
235    pub fn containing_directory(&self) -> &Path {
236      self
237        .path
238        .parent()
239        .expect("library path should have a parent directory!")
240    }
241
242    pub fn minus_l_arg(&self) -> String { format!("{}={}", self.kind.cargo_lib_type(), self.name) }
243
244    pub fn parse_file_path(file_path: &Path) -> Option<Self> {
245      static LIBNAME_PATTERN: Lazy<Regex> =
246        Lazy::new(|| Regex::new(r"^lib([^/]+)\.(a|so)$").unwrap());
247      let filename = match file_path.file_name() {
248        Some(component) => component.to_string_lossy(),
249        /* All files have a name, and we only care about files, so ignore directories like '..'
250         * which don't have a name. */
251        None => {
252          return None;
253        },
254      };
255      if let Some(m) = LIBNAME_PATTERN.captures(&filename) {
256        let name = LibraryName(m.get(1).unwrap().as_str().to_string());
257        let kind = LibraryType::match_filename_suffix(m.get(2).unwrap().as_str())
258          .expect("validated that only .a or .so files can match LIBNAME_PATTERN regex");
259        Some(Self {
260          path: file_path.to_path_buf(),
261          name,
262          kind,
263        })
264      } else {
265        None
266      }
267    }
268  }
269
270  #[derive(Debug, Clone)]
271  pub struct Prefix {
272    pub path: PathBuf,
273  }
274
275  impl Prefix {
276    pub fn traverse(&self) -> impl Stream<Item=Result<walkdir::DirEntry, walkdir::Error>> {
277      let path = self.path.clone();
278      try_stream! {
279        for file in walkdir::WalkDir::new(path) {
280          yield file?;
281        }
282      }
283    }
284
285    pub fn include_subdir(&self) -> PathBuf { self.path.join("include") }
286
287    pub fn lib_subdir(&self) -> PathBuf { self.path.join("lib") }
288  }
289
290  #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
291  pub enum SearchBehavior {
292    #[default]
293    ErrorOnDuplicateLibraryName,
294    SelectFirstForEachLibraryName,
295  }
296
297  #[derive(Debug, Clone, Default)]
298  pub struct LibsQuery {
299    pub needed_libraries: Vec<LibraryName>,
300    pub kind: LibraryType,
301    pub search_behavior: SearchBehavior,
302  }
303
304  impl LibsQuery {
305    pub async fn find_libs(self, prefix: &Prefix) -> Result<LibCollection, PrefixTraversalError> {
306      let Self {
307        needed_libraries,
308        kind,
309        search_behavior,
310      } = self;
311      let needed_libraries: IndexSet<LibraryName> = needed_libraries.iter().cloned().collect();
312      let mut libs_by_name: IndexMap<LibraryName, Vec<CABILibrary>> = IndexMap::new();
313
314      let s = prefix.traverse();
315      pin_mut!(s);
316
317      while let Some(dir_entry) = s.try_next().await? {
318        let lib_path = dir_entry.into_path();
319        match CABILibrary::parse_file_path(&lib_path) {
320          Some(lib) if lib.kind == kind => {
321            dbg!(&lib);
322            libs_by_name
323              .entry(lib.name.clone())
324              .or_insert_with(Vec::new)
325              .push(lib);
326          },
327          _ => (),
328        }
329      }
330
331      let found: IndexSet<LibraryName> = libs_by_name.keys().cloned().collect();
332      let needed_not_found: IndexSet<LibraryName> =
333        needed_libraries.difference(&found).cloned().collect();
334      if !needed_not_found.is_empty() {
335        return Err(PrefixTraversalError::NeededLibrariesNotFound(
336          needed_not_found,
337          prefix.clone(),
338          found,
339        ));
340      }
341      let only_needed_libs: IndexMap<LibraryName, Vec<CABILibrary>> = libs_by_name
342        .into_iter()
343        .filter(|(name, _)| needed_libraries.contains(name))
344        .collect();
345
346      let mut singly_matched_libs: IndexMap<LibraryName, CABILibrary> = IndexMap::new();
347      let mut duplicated_libs: IndexMap<LibraryName, Vec<CABILibrary>> = IndexMap::new();
348      for (name, mut libs) in only_needed_libs.into_iter() {
349        assert!(!libs.is_empty());
350        if libs.len() == 1 {
351          singly_matched_libs.insert(name, libs.pop().unwrap());
352        } else {
353          duplicated_libs.insert(name, libs);
354        }
355      }
356
357      match search_behavior {
358        SearchBehavior::ErrorOnDuplicateLibraryName => {
359          if !duplicated_libs.is_empty() {
360            return Err(PrefixTraversalError::DuplicateLibraryNames(
361              duplicated_libs.keys().cloned().collect(),
362              prefix.clone(),
363              duplicated_libs,
364            ));
365          }
366        },
367        SearchBehavior::SelectFirstForEachLibraryName => {
368          for (name, libs) in duplicated_libs.into_iter() {
369            assert!(!singly_matched_libs.contains_key(&name));
370            assert!(!libs.is_empty());
371            dbg!(&name);
372            dbg!(&libs);
373            singly_matched_libs.insert(name, libs.into_iter().next().unwrap());
374          }
375        },
376      }
377      assert_eq!(singly_matched_libs.len(), needed_libraries.len());
378
379      Ok(LibCollection {
380        found_libraries: singly_matched_libs.into_values().collect(),
381      })
382    }
383  }
384
385  #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
386  pub enum RpathBehavior {
387    #[default]
388    DoNotSetRpath,
389    SetRpathForContainingDirs,
390  }
391
392  #[derive(Debug, Clone)]
393  pub struct LibCollection {
394    pub found_libraries: Vec<CABILibrary>,
395  }
396
397  impl LibCollection {
398    pub fn link_libraries(self, rpath_behavior: RpathBehavior) {
399      let mut containing_dirs: IndexSet<PathBuf> = IndexSet::new();
400      for lib in self.found_libraries.into_iter() {
401        println!("cargo:rerun-if-changed={}", lib.path.display());
402        println!("cargo:rustc-link-lib={}", lib.minus_l_arg());
403        containing_dirs.insert(lib.containing_directory().to_path_buf());
404
405        if rpath_behavior == RpathBehavior::SetRpathForContainingDirs {
406          assert_eq!(lib.kind, LibraryType::Dynamic);
407        }
408      }
409
410      for dir in containing_dirs.iter() {
411        println!("cargo:rustc-link-search=native={}", dir.display());
412      }
413
414      if rpath_behavior == RpathBehavior::SetRpathForContainingDirs {
415        for dir in containing_dirs.iter() {
416          println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dir.display());
417        }
418        let joined_rpath: String = containing_dirs
419          .into_iter()
420          .map(|p| format!("{}", p.display()))
421          .collect::<Vec<_>>()
422          .join(":");
423        println!("cargo:joined_rpath={}", joined_rpath);
424      }
425    }
426  }
427}
428
429pub mod metadata {
430  use crate::metadata_spec::spec;
431  use super_process::{exe, sync::SyncInvocable};
432
433  use displaydoc::Display;
434  use guppy::CargoMetadata;
435  use indexmap::{IndexMap, IndexSet};
436  use serde_json;
437  use thiserror::Error;
438
439  use std::{env, io};
440
441  #[derive(Debug, Display, Error)]
442  pub enum MetadataError {
443    /// error processing specs {0}
444    Spec(#[from] spec::SpecError),
445    /// command line error {0}
446    Command(#[from] exe::CommandErrorWrapper),
447    /// io error {0}
448    Io(#[from] io::Error),
449    /// json error {0}
450    Json(#[from] serde_json::Error),
451    /// guppy error {0}
452    Guppy(#[from] guppy::Error),
453  }
454
455  pub async fn get_metadata() -> Result<spec::DisjointResolves, MetadataError> {
456    let cargo_exe: exe::Exe = env::var("CARGO").as_ref().unwrap().into();
457    let cargo_argv: exe::Argv = ["metadata", "--format-version", "1"].into();
458    let cargo_cmd = exe::Command {
459      exe: cargo_exe,
460      argv: cargo_argv,
461      ..Default::default()
462    };
463
464    let metadata =
465      CargoMetadata::parse_json(cargo_cmd.clone().invoke().await?.decode(cargo_cmd)?.stdout)?;
466    let package_graph = metadata.build_graph()?;
467
468    let labelled_metadata: Vec<(
469      spec::CrateName,
470      spec::LabelledPackageMetadata,
471      Vec<spec::CargoFeature>,
472    )> = package_graph
473      .packages()
474      .filter_map(|p| {
475        let name = spec::CrateName(p.name().to_string());
476        let spack_metadata = p.metadata_table().as_object()?.get("spack")?.clone();
477        dbg!(&spack_metadata);
478        let features: Vec<_> = p
479          .named_features()
480          .map(|s| spec::CargoFeature(s.to_string()))
481          .collect();
482        Some((name, spack_metadata, features))
483      })
484      .map(|(name, spack_metadata, features)| {
485        let spec_metadata: spec::LabelledPackageMetadata = serde_json::from_value(spack_metadata)?;
486        Ok((name, spec_metadata, features))
487      })
488      .collect::<Result<Vec<_>, MetadataError>>()?;
489
490    let mut resolves: IndexMap<spec::Label, (Option<spec::Repo>, Vec<spec::Spec>)> =
491      IndexMap::new();
492    let mut recipes: IndexMap<
493      spec::CrateName,
494      IndexMap<spec::Label, (spec::Recipe, spec::FeatureMap)>,
495    > = IndexMap::new();
496    let mut declared_features_by_package: IndexMap<spec::CrateName, Vec<spec::CargoFeature>> =
497      IndexMap::new();
498
499    for (crate_name, spec::LabelledPackageMetadata { envs, repo }, features) in
500      labelled_metadata.into_iter()
501    {
502      dbg!(&envs);
503
504      assert!(declared_features_by_package
505        .insert(crate_name.clone(), features)
506        .is_none());
507
508      for (label, env) in envs.into_iter() {
509        let spec::Env {
510          spec,
511          deps,
512          features,
513        } = env;
514        let env_label = spec::Label(label);
515        let spec = spec::Spec(spec);
516        let feature_map = if let Some(spec::FeatureLayout {
517          needed,
518          conflicting,
519        }) = features
520        {
521          let needed: IndexSet<_> = needed
522            .unwrap_or_default()
523            .into_iter()
524            .map(spec::CargoFeature)
525            .collect();
526          let conflicting: IndexSet<_> = conflicting
527            .unwrap_or_default()
528            .into_iter()
529            .map(spec::CargoFeature)
530            .collect();
531          spec::FeatureMap {
532            needed,
533            conflicting,
534          }
535        } else {
536          spec::FeatureMap::default()
537        };
538
539        let sub_deps: Vec<spec::SubDep> = deps
540          .into_iter()
541          .map(|(pkg_name, spec::Dep { r#type, lib_names })| {
542            Ok(spec::SubDep {
543              pkg_name: spec::PackageName(pkg_name),
544              r#type: r#type.parse()?,
545              lib_names,
546            })
547          })
548          .collect::<Result<Vec<_>, spec::SpecError>>()?;
549
550        let recipe = spec::Recipe { sub_deps };
551
552        let r = resolves.entry(env_label.clone()).or_default();
553        r.0 = repo.clone();
554        r.1.push(spec);
555        assert!(recipes
556          .entry(crate_name.clone())
557          .or_default()
558          .insert(env_label, (recipe, feature_map))
559          .is_none());
560      }
561    }
562
563    Ok(spec::DisjointResolves {
564      by_label: resolves
565        .into_iter()
566        .map(|(label, (repo, specs))| (label, spec::EnvInstructions { repo, specs }))
567        .collect(),
568      recipes,
569      declared_features_by_package,
570    })
571  }
572
573  pub fn get_cur_pkg_name() -> spec::CrateName {
574    spec::CrateName(env::var("CARGO_PKG_NAME").unwrap())
575  }
576}
577
578/// High-level API for build scripts that consumes `[package.metadata.spack]`.
579pub mod declarative {
580  pub mod resolve {
581    use crate::{
582      commands::*,
583      metadata_spec::spec,
584      subprocess::spack::SpackInvocation,
585      utils::{declarative::linker, metadata, prefix},
586    };
587
588    use std::borrow::Cow;
589
590    use indexmap::IndexMap;
591
592    fn currently_has_feature(feature: &spec::CargoFeature) -> bool {
593      std::env::var(feature.to_env_var_name()).is_ok()
594    }
595
596    async fn resolve_recipe(
597      env_label: spec::Label,
598      recipe: &spec::Recipe,
599      env_instructions: &spec::EnvInstructions,
600    ) -> eyre::Result<Vec<prefix::Prefix>> {
601      let env_hash = env_instructions.compute_digest();
602      let env = EnvName(env_hash.hashed_env_name(&env_label.0));
603      dbg!(&env);
604
605      /* Ensure spack is available. */
606      let spack = SpackInvocation::summon().await?;
607
608      /* Create the environment and install all specs into it if not already
609       * created. */
610      let env = env::EnvCreate {
611        spack: spack.clone(),
612        env,
613      }
614      .idempotent_env_create(Cow::Borrowed(env_instructions))
615      .await?;
616
617      let mut dep_prefixes: Vec<prefix::Prefix> = Vec::new();
618
619      /* Process each resolved dependency to hook it up into cargo. */
620      for sub_dep in recipe.sub_deps.iter() {
621        let env = env.clone();
622        let spack = spack.clone();
623        let req = find::FindPrefix {
624          spack: spack.clone(),
625          spec: CLISpec::new(&sub_dep.pkg_name.0),
626          env: Some(env.clone()),
627          repos: match env_instructions.repo.clone() {
628            Some(spec::Repo { path }) => {
629              let path = std::env::current_dir()?.join(path);
630              println!("cargo:rerun-if-changed={}", path.display());
631              Some(RepoDirs(vec![path]))
632            },
633            None => None,
634          },
635        };
636        let prefix = req.find_prefix().await?.unwrap();
637
638        linker::link_against_dependency(
639          prefix.clone(),
640          sub_dep.r#type,
641          sub_dep.lib_names.iter().map(|s| s.as_str()),
642        )
643        .await?;
644        dep_prefixes.push(prefix);
645      }
646
647      Ok(dep_prefixes)
648    }
649
650    pub async fn resolve_dependencies_for_label(
651      env_label: spec::Label,
652    ) -> eyre::Result<Vec<prefix::Prefix>> {
653      println!("cargo:rerun-if-changed=Cargo.toml");
654
655      /* Parse `cargo metadata` for the entire workspace. */
656      let metadata = metadata::get_metadata().await?;
657      /* Get the current package name. */
658      let cur_pkg_name = metadata::get_cur_pkg_name();
659
660      /* Get the current package's recipe with the given label. */
661      let declared_recipes: &IndexMap<spec::Label, (spec::Recipe, spec::FeatureMap)> =
662        metadata.recipes.get(&cur_pkg_name).unwrap();
663      dbg!(declared_recipes);
664      let specified_recipe: &spec::Recipe = declared_recipes
665        .get(&env_label)
666        .map(|(recipe, _)| recipe)
667        .ok_or_else(|| {
668          eyre::Report::msg(format!(
669            "unable to find label {:?} in declarations {:?}",
670            &env_label, declared_recipes,
671          ))
672        })?;
673
674      let env_instructions = metadata.by_label.get(&env_label).unwrap();
675      dbg!(env_instructions);
676
677      resolve_recipe(env_label, specified_recipe, env_instructions).await
678    }
679
680    pub async fn resolve_dependencies() -> eyre::Result<Vec<prefix::Prefix>> {
681      println!("cargo:rerun-if-changed=Cargo.toml");
682
683      /* Parse `cargo metadata` for the entire workspace. */
684      let metadata = metadata::get_metadata().await?;
685      /* Get the current package name. */
686      let cur_pkg_name = metadata::get_cur_pkg_name();
687
688      /* Get the current package's recipes. */
689      let declared_recipes: &IndexMap<spec::Label, (spec::Recipe, spec::FeatureMap)> =
690        metadata.recipes.get(&cur_pkg_name).unwrap();
691      dbg!(&declared_recipes);
692
693      /* Get the declared features for the current package. */
694      let declared_features = metadata
695        .declared_features_by_package
696        .get(&cur_pkg_name)
697        .unwrap();
698      let activated_features = spec::FeatureSet(
699        declared_features
700          .iter()
701          .filter(|f| currently_has_feature(f))
702          .cloned()
703          .collect(),
704      );
705
706      /* Get the recipe matching the current set of activated features. */
707      let matching_recipes: Vec<(&spec::Label, &spec::Recipe)> = declared_recipes
708        .into_iter()
709        .filter(|(_, (_, feature_map))| feature_map.evaluate(&activated_features))
710        .map(|(label, (recipe, _))| (label, recipe))
711        .collect();
712
713      let (env_label, cur_recipe) = match matching_recipes.len() {
714        0 => {
715          return Err(eyre::Report::msg(format!(
716            "no recipe found for given features {:?} from declared recipes {:?}",
717            activated_features, declared_recipes,
718          )))
719        },
720        1 => matching_recipes[0],
721        _ => {
722          return Err(eyre::Report::msg(format!(
723            "more than one recipe {:?} matched for given features {:?} from declared recipes {:?}",
724            matching_recipes, activated_features, declared_recipes,
725          )))
726        },
727      };
728      dbg!(&env_label);
729      dbg!(&cur_recipe);
730
731      /* Get a hashed name for the current environment to resolve. */
732      let env_instructions = metadata.by_label.get(env_label).unwrap();
733      dbg!(env_instructions);
734
735      resolve_recipe(env_label.clone(), cur_recipe, env_instructions).await
736    }
737  }
738
739  pub mod linker {
740    use crate::{metadata_spec::spec, utils::prefix};
741
742    pub async fn link_against_dependency(
743      prefix: prefix::Prefix,
744      r#type: spec::LibraryType,
745      lib_names: impl Iterator<Item=&str>,
746    ) -> eyre::Result<()> {
747      let needed_libraries: Vec<_> = lib_names
748        .map(|s| prefix::LibraryName(s.to_string()))
749        .collect();
750      let kind = match r#type {
751        spec::LibraryType::Static => prefix::LibraryType::Static,
752        spec::LibraryType::DynamicWithRpath => prefix::LibraryType::Dynamic,
753      };
754      let query = prefix::LibsQuery {
755        needed_libraries,
756        kind,
757        search_behavior: prefix::SearchBehavior::ErrorOnDuplicateLibraryName,
758      };
759      let libs = query.find_libs(&prefix).await?;
760
761      let rpath_behavior = match kind {
762        /* No rpath necessary for static linking. */
763        prefix::LibraryType::Static => prefix::RpathBehavior::DoNotSetRpath,
764        prefix::LibraryType::Dynamic => prefix::RpathBehavior::SetRpathForContainingDirs,
765      };
766      libs.link_libraries(rpath_behavior);
767
768      Ok(())
769    }
770  }
771}