Skip to main content

wasm_pkg_core/
resolver.rs

1//! A resolver for resolving dependencies from a component registry.
2// NOTE(thomastaylor312): This is copied and adapted from the `cargo-component` crate: https://github.com/bytecodealliance/cargo-component/blob/f0be1c7d9917aa97e9102e69e3b838dae38d624b/crates/core/src/registry.rs
3
4use std::{
5    collections::{hash_map, HashMap, HashSet},
6    fmt::Debug,
7    ops::{Deref, DerefMut},
8    path::{Path, PathBuf},
9    str::FromStr,
10};
11
12use anyhow::{bail, Context, Result};
13use futures_util::TryStreamExt;
14use indexmap::{IndexMap, IndexSet};
15use semver::{Comparator, Op, Version, VersionReq};
16use tokio::io::{AsyncRead, AsyncReadExt};
17use wasm_pkg_client::{
18    caching::{CachingClient, FileCache},
19    Client, Config, ContentDigest, Error as WasmPkgError, PackageRef, Release, VersionInfo,
20};
21use wit_component::DecodedWasm;
22use wit_parser::{PackageId, PackageName, Resolve, UnresolvedPackageGroup, WorldId};
23
24use crate::{lock::LockFile, wit::get_packages};
25
26/// The name of the default registry.
27pub const DEFAULT_REGISTRY_NAME: &str = "default";
28
29// TODO: functions for resolving dependencies from a lock file
30
31/// Represents a WIT package dependency.
32#[derive(Debug, Clone)]
33pub enum Dependency {
34    /// The dependency is a registry package.
35    Package(RegistryPackage),
36
37    /// The dependency is a path to a local directory or file.
38    Local(PathBuf),
39}
40
41impl std::fmt::Display for Dependency {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Dependency::Package(RegistryPackage {
45                name,
46                version,
47                registry,
48            }) => {
49                let registry = registry.as_deref().unwrap_or("_");
50                let name = name.as_ref().map(|n| n.to_string());
51
52                write!(
53                    f,
54                    "{{registry=\"{registry}\" package=\"{}@{version}\"}}",
55                    name.as_deref().unwrap_or("_:_"),
56                )
57            }
58            Dependency::Local(path_buf) => write!(f, "{}", path_buf.display()),
59        }
60    }
61}
62
63impl FromStr for Dependency {
64    type Err = anyhow::Error;
65
66    fn from_str(s: &str) -> Result<Self> {
67        Ok(Self::Package(s.parse()?))
68    }
69}
70
71/// Represents a reference to a registry package.
72#[derive(Debug, Clone)]
73pub struct RegistryPackage {
74    /// The name of the package.
75    ///
76    /// If not specified, the name from the mapping will be used.
77    pub name: Option<PackageRef>,
78
79    /// The version requirement of the package.
80    pub version: VersionReq,
81
82    /// The name of the component registry containing the package.
83    ///
84    /// If not specified, the default registry is used.
85    pub registry: Option<String>,
86}
87
88impl FromStr for RegistryPackage {
89    type Err = anyhow::Error;
90
91    fn from_str(s: &str) -> Result<Self> {
92        Ok(Self {
93            name: None,
94            version: s
95                .parse()
96                .with_context(|| format!("'{s}' is an invalid registry package version"))?,
97            registry: None,
98        })
99    }
100}
101
102/// Represents information about a resolution of a registry package.
103#[derive(Clone)]
104pub struct RegistryResolution {
105    /// The name of the dependency that was resolved.
106    ///
107    /// This may differ from `package` if the dependency was renamed.
108    pub name: PackageRef,
109    /// The name of the package from the registry that was resolved.
110    pub package: PackageRef,
111    /// The name of the registry used to resolve the package if one was provided
112    pub registry: Option<String>,
113    /// The version requirement that was used to resolve the package.
114    pub requirement: VersionReq,
115    /// The package version that was resolved.
116    pub version: Version,
117    /// The digest of the package contents.
118    pub digest: ContentDigest,
119    /// The client to use for fetching the package contents.
120    client: CachingClient<FileCache>,
121}
122
123impl Debug for RegistryResolution {
124    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
125        f.debug_struct("RegistryResolution")
126            .field("name", &self.name)
127            .field("package", &self.package)
128            .field("registry", &self.registry)
129            .field("requirement", &self.requirement)
130            .field("version", &self.version)
131            .field("digest", &self.digest)
132            .finish()
133    }
134}
135
136impl RegistryResolution {
137    /// Fetches the raw package bytes from the registry. Returns an AsyncRead that will stream the
138    /// package contents
139    pub async fn fetch(&self) -> Result<impl AsyncRead> {
140        let stream = self
141            .client
142            .get_content(
143                &self.package,
144                &Release {
145                    version: self.version.clone(),
146                    content_digest: self.digest.clone(),
147                },
148            )
149            .await?;
150
151        Ok(tokio_util::io::StreamReader::new(
152            stream.map_err(std::io::Error::other),
153        ))
154    }
155}
156
157/// Represents information about a resolution of a local file.
158#[derive(Clone, Debug)]
159pub struct LocalResolution {
160    /// The name of the dependency that was resolved.
161    pub name: PackageRef,
162    /// The path to the resolved dependency.
163    pub path: PathBuf,
164}
165
166/// Represents a resolution of a dependency.
167#[derive(Debug, Clone)]
168#[allow(clippy::large_enum_variant)]
169pub enum DependencyResolution {
170    /// The dependency is resolved from a registry package.
171    Registry(RegistryResolution),
172    /// The dependency is resolved from a local path.
173    Local(LocalResolution),
174}
175
176impl DependencyResolution {
177    /// Gets the name of the dependency that was resolved.
178    pub fn name(&self) -> &PackageRef {
179        match self {
180            Self::Registry(res) => &res.name,
181            Self::Local(res) => &res.name,
182        }
183    }
184
185    /// Gets the resolved version.
186    ///
187    /// Returns `None` if the dependency is not resolved from a registry package.
188    pub fn version(&self) -> Option<&Version> {
189        match self {
190            Self::Registry(res) => Some(&res.version),
191            Self::Local(_) => None,
192        }
193    }
194
195    /// The key used in sorting and searching the lock file package list.
196    ///
197    /// Returns `None` if the dependency is not resolved from a registry package.
198    pub fn key(&self) -> Option<(&PackageRef, Option<&str>)> {
199        match self {
200            DependencyResolution::Registry(pkg) => Some((&pkg.package, pkg.registry.as_deref())),
201            DependencyResolution::Local(_) => None,
202        }
203    }
204
205    /// Decodes the resolved dependency.
206    pub async fn decode(&self) -> Result<DecodedDependency<'_>> {
207        // If the dependency path is a directory, assume it contains wit to parse as a package.
208        let bytes = match self {
209            DependencyResolution::Local(LocalResolution { path, .. })
210                if tokio::fs::metadata(path).await?.is_dir() =>
211            {
212                return Ok(DecodedDependency::Wit {
213                    resolution: self,
214                    package: UnresolvedPackageGroup::parse_dir(path).with_context(|| {
215                        format!("failed to parse dependency `{path}`", path = path.display())
216                    })?,
217                });
218            }
219            DependencyResolution::Local(LocalResolution { path, .. }) => {
220                tokio::fs::read(path).await.with_context(|| {
221                    format!(
222                        "failed to read content of dependency `{name}` at path `{path}`",
223                        name = self.name(),
224                        path = path.display()
225                    )
226                })?
227            }
228            DependencyResolution::Registry(res) => {
229                let mut reader = res.fetch().await?;
230
231                let mut buf = Vec::new();
232                reader.read_to_end(&mut buf).await?;
233                buf
234            }
235        };
236
237        if &bytes[0..4] != b"\0asm" {
238            return Ok(DecodedDependency::Wit {
239                resolution: self,
240                package: UnresolvedPackageGroup::parse(
241                    // This is fake, but it's needed for the parser to work.
242                    self.name().to_string(),
243                    std::str::from_utf8(&bytes).with_context(|| {
244                        format!(
245                            "dependency `{name}` is not UTF-8 encoded",
246                            name = self.name()
247                        )
248                    })?,
249                )?,
250            });
251        }
252
253        Ok(DecodedDependency::Wasm {
254            resolution: self,
255            decoded: wit_component::decode(&bytes).with_context(|| {
256                format!(
257                    "failed to decode content of dependency `{name}`",
258                    name = self.name(),
259                )
260            })?,
261        })
262    }
263}
264
265/// Represents a decoded dependency.
266pub enum DecodedDependency<'a> {
267    /// The dependency decoded from an unresolved WIT package.
268    Wit {
269        /// The resolution related to the decoded dependency.
270        resolution: &'a DependencyResolution,
271        /// The unresolved WIT package.
272        package: UnresolvedPackageGroup,
273    },
274    /// The dependency decoded from a Wasm file.
275    Wasm {
276        /// The resolution related to the decoded dependency.
277        resolution: &'a DependencyResolution,
278        /// The decoded Wasm file.
279        decoded: DecodedWasm,
280    },
281}
282
283impl DecodedDependency<'_> {
284    /// Fully resolves the dependency.
285    ///
286    /// If the dependency is an unresolved WIT package, it will assume that the
287    /// package has no foreign dependencies.
288    pub fn resolve(self) -> Result<(Resolve, PackageId, Vec<PathBuf>)> {
289        match self {
290            Self::Wit { package, .. } => {
291                let mut resolve = Resolve::new();
292                resolve.all_features = true;
293                let source_files = package
294                    .source_map
295                    .source_files()
296                    .map(Path::to_path_buf)
297                    .collect();
298                let pkg = resolve.push_group(package)?;
299                Ok((resolve, pkg, source_files))
300            }
301            Self::Wasm { decoded, .. } => match decoded {
302                DecodedWasm::WitPackage(resolve, pkg) => Ok((resolve, pkg, Vec::new())),
303                DecodedWasm::Component(resolve, world) => {
304                    let pkg = resolve.worlds[world].package.unwrap();
305                    Ok((resolve, pkg, Vec::new()))
306                }
307            },
308        }
309    }
310
311    /// Gets the package name of the decoded dependency.
312    pub fn package_name(&self) -> &PackageName {
313        match self {
314            Self::Wit { package, .. } => &package.main.name,
315            Self::Wasm { decoded, .. } => &decoded.resolve().packages[decoded.package()].name,
316        }
317    }
318
319    /// Converts the decoded dependency into a component world.
320    ///
321    /// Returns an error if the dependency is not a decoded component.
322    pub fn into_component_world(self) -> Result<(Resolve, WorldId)> {
323        match self {
324            Self::Wasm {
325                decoded: DecodedWasm::Component(resolve, world),
326                ..
327            } => Ok((resolve, world)),
328            _ => bail!("dependency is not a WebAssembly component"),
329        }
330    }
331}
332
333/// Used to resolve dependencies for a WIT package.
334pub struct DependencyResolver<'a> {
335    client: CachingClient<FileCache>,
336    lock_file: Option<&'a LockFile>,
337    packages: HashMap<PackageRef, Vec<VersionInfo>>,
338    dependencies: HashMap<PackageRef, RegistryDependency>,
339    resolutions: DependencyResolutionMap,
340}
341
342impl<'a> DependencyResolver<'a> {
343    /// Creates a new dependency resolver. If `config` is `None`, then the resolver will be set to
344    /// offline mode and a lock file must be given as well. Anything that will require network
345    /// access will fail in offline mode.
346    pub fn new(
347        config: Option<Config>,
348        lock_file: Option<&'a LockFile>,
349        cache: FileCache,
350    ) -> anyhow::Result<Self> {
351        if config.is_none() && lock_file.is_none() {
352            anyhow::bail!("lock file must be provided when offline mode is enabled");
353        }
354        let client = CachingClient::new(config.map(Client::new), cache);
355        Ok(DependencyResolver {
356            client,
357            lock_file,
358            resolutions: Default::default(),
359            packages: Default::default(),
360            dependencies: Default::default(),
361        })
362    }
363
364    /// Creates a new dependency resolver with the given client. This is useful when you already
365    /// have a client available. If the client is set to offline mode, then a lock file must be
366    /// given or this will error
367    pub fn new_with_client(
368        client: CachingClient<FileCache>,
369        lock_file: Option<&'a LockFile>,
370    ) -> anyhow::Result<Self> {
371        if client.is_readonly() && lock_file.is_none() {
372            anyhow::bail!("lock file must be provided when offline mode is enabled");
373        }
374        Ok(DependencyResolver {
375            client,
376            lock_file,
377            resolutions: Default::default(),
378            packages: Default::default(),
379            dependencies: Default::default(),
380        })
381    }
382
383    /// Add a dependency to the resolver. If the dependency already exists, then it will be ignored.
384    /// To override an existing dependency, use [`override_dependency`](Self::override_dependency).
385    pub async fn add_dependency(
386        &mut self,
387        name: &PackageRef,
388        dependency: &Dependency,
389    ) -> Result<()> {
390        self.add_dependency_internal(name, dependency, false).await
391    }
392
393    /// Add a dependency to the resolver. If the dependency already exists, then it will be
394    /// overridden.
395    pub async fn override_dependency(
396        &mut self,
397        name: &PackageRef,
398        dependency: &Dependency,
399    ) -> Result<()> {
400        self.add_dependency_internal(name, dependency, true).await
401    }
402
403    async fn add_dependency_internal(
404        &mut self,
405        name: &PackageRef,
406        dependency: &Dependency,
407        force_override: bool,
408    ) -> Result<()> {
409        match dependency {
410            Dependency::Package(package) => {
411                // Dependency comes from a registry, add a dependency to the resolver
412                let registry_name = package.registry.as_deref().or_else(|| {
413                    self.client.client().ok().and_then(|client| {
414                        client
415                            .config()
416                            .resolve_registry(name)
417                            .map(|reg| reg.as_ref())
418                    })
419                });
420                let package_name = package.name.clone().unwrap_or_else(|| name.clone());
421
422                // Resolve the version from the lock file if there is one
423                let locked = match self.lock_file.as_ref().and_then(|resolver| {
424                    resolver
425                        .resolve(registry_name, &package_name, &package.version)
426                        .transpose()
427                }) {
428                    Some(Ok(locked)) => Some(locked),
429                    Some(Err(e)) => return Err(e),
430                    _ => None,
431                };
432
433                // So if it wasn't already fetched first? then we'll try and resolve it later, and the override
434                // is not present there for some reason
435                if !force_override
436                    && (self.resolutions.contains_key(name) || self.dependencies.contains_key(name))
437                {
438                    tracing::debug!(%name, %dependency, "dependency already exists and override is not set, ignoring");
439                    return Ok(());
440                }
441                self.dependencies.insert(
442                    name.to_owned(),
443                    RegistryDependency {
444                        package: package_name,
445                        version: package.version.clone(),
446                        locked: locked.map(|l| (l.version.clone(), l.digest.clone())),
447                    },
448                );
449            }
450            Dependency::Local(p) => {
451                let res = DependencyResolution::Local(LocalResolution {
452                    name: name.clone(),
453                    path: p.clone(),
454                });
455
456                // This is a bit of a hack, but if there are multiple local dependencies that are
457                // nested and overridden, getting the packages from the local package treats _all_
458                // deps as registry deps. So if we're handling a local path and the dependencies
459                // have a registry package already, override it. Otherwise follow normal overrides.
460                // We should definitely fix this and change where we resolve these things
461                let should_insert = force_override
462                    || self.dependencies.contains_key(name)
463                    || !self.resolutions.contains_key(name);
464                if !should_insert {
465                    tracing::debug!(%name, "dependency already exists and registry override is not set, ignoring");
466                    return Ok(());
467                }
468
469                // Because we got here, we should remove anything from dependencies that is the same
470                // package because we're overriding with the local package. Technically we could be
471                // clever and just do this in the boolean above, but I'm paranoid
472                self.dependencies.remove(name);
473
474                // Now that we check we haven't already inserted this dep, get the packages from the
475                // local dependency and add those to the resolver before adding the dependency
476                let (_, packages) = get_packages(p)
477                    .context("Error getting dependent packages from local dependency")?;
478                Box::pin(self.add_packages(packages))
479                    .await
480                    .context("Error adding packages to resolver for local dependency")?;
481
482                let prev = self.resolutions.insert(name.clone(), res);
483                assert!(prev.is_none());
484            }
485        }
486
487        Ok(())
488    }
489
490    /// A helper function for adding an iterator of package refs and their associated version
491    /// requirements to the resolver
492    pub async fn add_packages(
493        &mut self,
494        packages: impl IntoIterator<Item = (PackageRef, VersionReq)>,
495    ) -> Result<()> {
496        for (package, req) in packages {
497            self.add_dependency(
498                &package,
499                &Dependency::Package(RegistryPackage {
500                    name: Some(package.clone()),
501                    version: req,
502                    registry: None,
503                }),
504            )
505            .await?;
506        }
507        Ok(())
508    }
509
510    /// Resolve all dependencies.
511    ///
512    /// This will download all dependencies that are not already present in client storage.
513    ///
514    /// Returns the dependency resolution map.
515    pub async fn resolve(mut self) -> Result<DependencyResolutionMap> {
516        let mut resolutions = self.resolutions;
517        for (name, dependency) in self.dependencies.into_iter() {
518            // We need to clone a handle to the client because we mutably borrow self below. Might
519            // be worth replacing the mutable borrow with a RwLock down the line.
520            let client = self.client.clone();
521
522            let (selected_version, digest) = if client.is_readonly() {
523                dependency
524                    .locked
525                    .as_ref()
526                    .map(|(ver, digest)| (ver, Some(digest)))
527                    .ok_or_else(|| {
528                        anyhow::anyhow!("Couldn't find locked dependency while in offline mode")
529                    })?
530            } else {
531                let versions =
532                    load_package(&mut self.packages, &self.client, dependency.package.clone())
533                        .await?
534                        .with_context(|| {
535                            format!(
536                                "package `{name}` was not found in component registry",
537                                name = dependency.package
538                            )
539                        })?;
540
541                match &dependency.locked {
542                    Some((version, digest)) => {
543                        // The dependency had a lock file entry, so attempt to do an exact match first
544                        let exact_req = VersionReq {
545                            comparators: vec![Comparator {
546                                op: Op::Exact,
547                                major: version.major,
548                                minor: Some(version.minor),
549                                patch: Some(version.patch),
550                                pre: version.pre.clone(),
551                            }],
552                        };
553
554                        // If an exact match can't be found, fallback to the latest release to satisfy
555                        // the version requirement; this can happen when packages are yanked. If we did
556                        // find an exact match, return the digest for comparison after fetching the
557                        // release
558                        find_latest_release(versions, &exact_req).map(|v| (&v.version, Some(digest))).or_else(|| find_latest_release(versions, &dependency.version).map(|v| (&v.version, None)))
559                    }
560                    None => find_latest_release(versions, &dependency.version).map(|v| (&v.version, None)),
561                }.with_context(|| format!("component registry package `{name}` has no release matching version requirement `{version}`", name = dependency.package, version = dependency.version))?
562            };
563
564            // We need to clone a handle to the client because we mutably borrow self above. Might
565            // be worth replacing the mutable borrow with a RwLock down the line.
566            let release = client
567                .get_release(&dependency.package, selected_version)
568                .await?;
569            if let Some(digest) = digest {
570                if &release.content_digest != digest {
571                    bail!(
572                        "component registry package `{name}` (v`{version}`) has digest `{content}` but the lock file specifies digest `{digest}`",
573                        name = dependency.package,
574                        version = release.version,
575                        content = release.content_digest,
576                    );
577                }
578            }
579            let resolution = RegistryResolution {
580                name: name.clone(),
581                package: dependency.package.clone(),
582                registry: self.client.client().ok().and_then(|client| {
583                    client
584                        .config()
585                        .resolve_registry(&name)
586                        .map(ToString::to_string)
587                }),
588                requirement: dependency.version.clone(),
589                version: release.version.clone(),
590                digest: release.content_digest.clone(),
591                client: self.client.clone(),
592            };
593            resolutions.insert(name, DependencyResolution::Registry(resolution));
594        }
595
596        Ok(resolutions)
597    }
598}
599
600async fn load_package<'b>(
601    packages: &'b mut HashMap<PackageRef, Vec<VersionInfo>>,
602    client: &CachingClient<FileCache>,
603    package: PackageRef,
604) -> Result<Option<&'b Vec<VersionInfo>>> {
605    match packages.entry(package) {
606        hash_map::Entry::Occupied(e) => Ok(Some(e.into_mut())),
607        hash_map::Entry::Vacant(e) => match client.list_all_versions(e.key()).await {
608            Ok(p) => Ok(Some(e.insert(p))),
609            Err(WasmPkgError::PackageNotFound) => Ok(None),
610            Err(err) => Err(err.into()),
611        },
612    }
613}
614
615#[derive(Debug)]
616struct RegistryDependency {
617    /// The canonical package name of the registry package. In most cases, this is the same as the
618    /// name but could be different if the given package name has been remapped
619    package: PackageRef,
620    version: VersionReq,
621    locked: Option<(Version, ContentDigest)>,
622}
623
624fn find_latest_release<'a>(
625    versions: &'a [VersionInfo],
626    req: &VersionReq,
627) -> Option<&'a VersionInfo> {
628    versions
629        .iter()
630        .filter(|info| !info.yanked && req.matches(&info.version))
631        .max_by(|a, b| a.version.cmp(&b.version))
632}
633
634// NOTE(thomastaylor312): This is copied from the old wit package in the cargo-component and broken
635// out for some reuse. I don't know enough about resolvers to know if there is an easier way to
636// write this, so any future people seeing this should feel free to refactor it if they know a
637// better way to do it.
638
639/// Represents a map of dependency resolutions.
640///
641/// The key to the map is the package name of the dependency.
642#[derive(Debug, Clone, Default)]
643pub struct DependencyResolutionMap(HashMap<PackageRef, DependencyResolution>);
644
645impl AsRef<HashMap<PackageRef, DependencyResolution>> for DependencyResolutionMap {
646    fn as_ref(&self) -> &HashMap<PackageRef, DependencyResolution> {
647        &self.0
648    }
649}
650
651impl Deref for DependencyResolutionMap {
652    type Target = HashMap<PackageRef, DependencyResolution>;
653
654    fn deref(&self) -> &Self::Target {
655        &self.0
656    }
657}
658
659impl DerefMut for DependencyResolutionMap {
660    fn deref_mut(&mut self) -> &mut Self::Target {
661        &mut self.0
662    }
663}
664
665impl DependencyResolutionMap {
666    /// Fetch all dependencies and ensure there are no circular dependencies. Returns the decoded
667    /// dependencies (sorted topologically), ready to use for output or adding to a [`Resolve`].
668    pub async fn decode_dependencies(
669        &self,
670    ) -> Result<IndexMap<PackageName, DecodedDependency<'_>>> {
671        // Start by decoding all of the dependencies
672        let mut deps = IndexMap::new();
673        for (name, resolution) in self.0.iter() {
674            let decoded = resolution.decode().await?;
675            if let Some(prev) = deps.insert(decoded.package_name().clone(), decoded) {
676                anyhow::bail!(
677                    "duplicate definitions of package `{prev}` found while decoding dependency `{name}`",
678                    prev = prev.package_name()
679                );
680            }
681        }
682
683        // Do a topological sort of the dependencies
684        let mut order = IndexSet::new();
685        let mut visiting = HashSet::new();
686        for dep in deps.values() {
687            visit(dep, &deps, &mut order, &mut visiting)?;
688        }
689
690        assert!(visiting.is_empty());
691
692        // Now that we have the right order, re-order the dependencies to match
693        deps.sort_by(|name_a, _, name_b, _| {
694            order.get_index_of(name_a).cmp(&order.get_index_of(name_b))
695        });
696
697        Ok(deps)
698    }
699
700    /// Given a path to a component or a directory containing wit, use the given dependencies to
701    /// generate a [`Resolve`] for the root package.
702    pub async fn generate_resolve(&self, dir: impl AsRef<Path>) -> Result<(Resolve, PackageId)> {
703        let mut merged = Resolve {
704            // Retain @unstable features; downstream tooling will process them further
705            all_features: true,
706            ..Resolve::default()
707        };
708
709        let deps = self.decode_dependencies().await?;
710
711        // Parse the root package itself
712        let root = UnresolvedPackageGroup::parse_dir(&dir).with_context(|| {
713            format!(
714                "failed to parse package from directory `{dir}`",
715                dir = dir.as_ref().display()
716            )
717        })?;
718
719        let mut source_files: Vec<_> = root
720            .source_map
721            .source_files()
722            .map(Path::to_path_buf)
723            .collect();
724
725        // Merge all of the dependencies first
726        for decoded in deps.into_values() {
727            match decoded {
728                DecodedDependency::Wit {
729                    resolution,
730                    package,
731                } => {
732                    source_files.extend(package.source_map.source_files().map(Path::to_path_buf));
733                    merged.push_group(package).with_context(|| {
734                        format!(
735                            "failed to merge dependency `{name}`",
736                            name = resolution.name()
737                        )
738                    })?;
739                }
740                DecodedDependency::Wasm {
741                    resolution,
742                    decoded,
743                } => {
744                    let resolve = match decoded {
745                        DecodedWasm::WitPackage(resolve, _) => resolve,
746                        DecodedWasm::Component(resolve, _) => resolve,
747                    };
748
749                    merged.merge(resolve).with_context(|| {
750                        format!(
751                            "failed to merge world of dependency `{name}`",
752                            name = resolution.name()
753                        )
754                    })?;
755                }
756            };
757        }
758
759        let package = merged.push_group(root).with_context(|| {
760            format!(
761                "failed to merge package from directory `{dir}`",
762                dir = dir.as_ref().display()
763            )
764        })?;
765
766        Ok((merged, package))
767    }
768}
769
770fn visit<'a>(
771    dep: &'a DecodedDependency<'a>,
772    deps: &'a IndexMap<PackageName, DecodedDependency>,
773    order: &mut IndexSet<PackageName>,
774    visiting: &mut HashSet<&'a PackageName>,
775) -> Result<()> {
776    if order.contains(dep.package_name()) {
777        return Ok(());
778    }
779
780    // Visit any unresolved foreign dependencies
781    match dep {
782        DecodedDependency::Wit {
783            package,
784            resolution,
785        } => {
786            for name in package.main.foreign_deps.keys() {
787                // Only visit known dependencies
788                // wit-parser will error on unknown foreign dependencies when
789                // the package is resolved
790                if let Some(dep) = deps.get(name) {
791                    if !visiting.insert(name) {
792                        anyhow::bail!("foreign dependency `{name}` forms a dependency cycle while parsing dependency `{other}`", other = resolution.name());
793                    }
794
795                    visit(dep, deps, order, visiting)?;
796                    assert!(visiting.remove(name));
797                }
798            }
799        }
800        DecodedDependency::Wasm {
801            decoded,
802            resolution,
803        } => {
804            // Look for foreign packages in the decoded dependency
805            for (_, package) in &decoded.resolve().packages {
806                if package.name.namespace == dep.package_name().namespace
807                    && package.name.name == dep.package_name().name
808                {
809                    continue;
810                }
811
812                if let Some(dep) = deps.get(&package.name) {
813                    if !visiting.insert(&package.name) {
814                        anyhow::bail!("foreign dependency `{name}` forms a dependency cycle while parsing dependency `{other}`", name = package.name, other = resolution.name());
815                    }
816
817                    visit(dep, deps, order, visiting)?;
818                    assert!(visiting.remove(&package.name));
819                }
820            }
821        }
822    }
823
824    assert!(order.insert(dep.package_name().clone()));
825
826    Ok(())
827}