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