1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
use std::{collections::BTreeMap, ops::Index, path::PathBuf};

use petgraph::{
    graph::{DiGraph, NodeIndex},
    visit::EdgeRef,
};
use wasmer_config::package::PackageId;

use crate::runtime::resolver::{DistributionInfo, PackageInfo};

#[derive(Debug, Clone)]
pub struct Resolution {
    pub package: ResolvedPackage,
    pub graph: DependencyGraph,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ItemLocation {
    /// The item's original name.
    pub name: String,
    /// The package this item comes from.
    pub package: PackageId,
}

/// An acyclic, directed dependency graph.
#[derive(Debug, Clone)]
pub struct DependencyGraph {
    root: NodeIndex,
    graph: DiGraph<Node, Edge>,
    packages: BTreeMap<PackageId, NodeIndex>,
}

impl DependencyGraph {
    pub(crate) fn new(
        root: NodeIndex,
        graph: DiGraph<Node, Edge>,
        packages: BTreeMap<PackageId, NodeIndex>,
    ) -> Self {
        if cfg!(debug_assertions) {
            // Note: We assume the packages table correctly maps PackageIds to
            // node indices as part of the PartialEq implementation.
            for (id, index) in &packages {
                let node = &graph[*index];
                assert_eq!(*id, node.id, "Mismatch for node {index:?}");
            }
        }
        debug_assert!(
            packages.values().any(|ix| *ix == root),
            "The packages mapping doesn't contain the root node"
        );

        DependencyGraph {
            root,
            graph,
            packages,
        }
    }

    pub fn root_info(&self) -> &PackageInfo {
        let Node { pkg, .. } = &self.graph[self.root];
        pkg
    }

    pub fn id(&self) -> &PackageId {
        let Node { id, .. } = &self.graph[self.root];
        id
    }

    pub fn root(&self) -> NodeIndex {
        self.root
    }

    pub fn graph(&self) -> &DiGraph<Node, Edge> {
        &self.graph
    }

    /// Get a mapping from [`PackageId`]s to [`NodeIndex`]s.
    pub fn packages(&self) -> &BTreeMap<PackageId, NodeIndex> {
        &self.packages
    }

    /// Get an iterator over all the packages in this dependency graph and their
    /// dependency mappings.
    pub fn iter_dependencies(
        &self,
    ) -> impl Iterator<Item = (&'_ PackageId, BTreeMap<&'_ str, &'_ PackageId>)> + '_ {
        self.packages.iter().map(move |(id, index)| {
            let dependencies: BTreeMap<_, _> = self
                .graph
                .edges(*index)
                .map(|edge_ref| {
                    (
                        edge_ref.weight().alias.as_str(),
                        &self.graph[edge_ref.target()].id,
                    )
                })
                .collect();
            (id, dependencies)
        })
    }

    /// Visualise this graph as a DOT program.
    pub fn visualise(&self) -> String {
        let graph = self.graph.map(|_, node| &node.id, |_, edge| &edge.alias);
        petgraph::dot::Dot::new(&graph).to_string()
    }
}

impl Index<NodeIndex> for DependencyGraph {
    type Output = Node;

    #[track_caller]
    fn index(&self, index: NodeIndex) -> &Self::Output {
        &self.graph[index]
    }
}

impl Index<&NodeIndex> for DependencyGraph {
    type Output = Node;

    #[track_caller]
    fn index(&self, index: &NodeIndex) -> &Self::Output {
        &self[*index]
    }
}

impl Index<&PackageId> for DependencyGraph {
    type Output = Node;

    #[track_caller]
    fn index(&self, index: &PackageId) -> &Self::Output {
        let index = self.packages[index];
        &self[index]
    }
}

impl PartialEq for DependencyGraph {
    fn eq(&self, other: &Self) -> bool {
        let DependencyGraph {
            root,
            graph,
            packages,
        } = self;

        // Make sure their roots are the same package
        let this_root = graph.node_weight(*root);
        let other_root = other.graph.node_weight(other.root);

        match (this_root, other_root) {
            (Some(lhs), Some(rhs)) if lhs == rhs => {}
            _ => return false,
        }

        // the packages table *should* just be an optimisation. We've checked
        // it is valid as part of DependencyGraph::new() and the entire graph
        // is immutable, so it's fine to ignore.
        let _ = packages;

        // Most importantly, the graphs should be "the same" (i.e. if a node
        // in one graph is a
        // nodes are connected to the same nodes in both)
        petgraph::algo::is_isomorphic_matching(graph, &other.graph, Node::eq, Edge::eq)
    }
}

impl Eq for DependencyGraph {}

/// A node in the [`DependencyGraph`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
    pub id: PackageId,
    pub pkg: PackageInfo,
    /// Information about how the package is distributed.
    ///
    /// This will only ever be missing for the root package.
    pub dist: Option<DistributionInfo>,
}

/// An edge in the [`DependencyGraph`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edge {
    /// The name used by the package when referring to this dependency.
    pub alias: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedFileSystemMapping {
    // TODO: Change this to a new type that isn't coupled to the OS
    pub mount_path: PathBuf,
    pub volume_name: String,
    pub original_path: Option<String>,
    pub package: PackageId,
}

/// A package that has been resolved, but is not yet runnable.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedPackage {
    pub root_package: PackageId,
    pub commands: BTreeMap<String, ItemLocation>,
    pub entrypoint: Option<String>,
    /// A mapping from paths to the volumes that should be mounted there.
    /// Note: mappings at the start of the list obscure mappings at the end of the list
    /// thus this list represents an inheritance tree
    pub filesystem: Vec<ResolvedFileSystemMapping>,
}