1use crate::{
4 manifest::{self, Dependency, ManifestFile},
5 source::{self, Source},
6};
7use manifest::ManifestFileError;
8use petgraph::{visit::EdgeRef, Directed, Direction};
9use serde::{Deserialize, Serialize};
10use std::{
11 collections::{hash_map, BTreeMap, HashMap, HashSet},
12 fmt,
13 hash::{Hash, Hasher},
14 path::{Path, PathBuf},
15 str,
16};
17use thiserror::Error;
18
19type GraphIx = u32;
21pub type Graph = petgraph::stable_graph::StableGraph<Pinned, Dep, Directed, GraphIx>;
23pub type EdgeIx = petgraph::graph::EdgeIndex<GraphIx>;
25pub type NodeIx = petgraph::graph::NodeIndex<GraphIx>;
27type EdgeReference<'a> = petgraph::stable_graph::EdgeReference<'a, Dep, GraphIx>;
29
30#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct Dep {
33 pub kind: DepKind,
35 pub name: String,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
46pub enum DepKind {
47 Library,
49 Contract,
51}
52
53#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
55pub struct Pkg {
56 pub name: String,
58 pub source: Source,
60}
61
62#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
64pub struct Pinned {
65 pub name: String,
67 pub source: source::Pinned,
69}
70
71#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
75pub struct PinnedId(u64);
76
77pub type PinnedManifests = HashMap<PinnedId, ManifestFile>;
79
80pub type MemberManifests = BTreeMap<String, ManifestFile>;
85
86pub type InvalidDeps = BTreeMap<EdgeIx, InvalidDepCause>;
88
89type FetchedPkgs = HashMap<Pkg, NodeIx>;
91
92struct FetchCtx<'a> {
94 id: source::FetchId,
95 fetched: &'a mut FetchedPkgs,
96 visited: &'a mut HashSet<NodeIx>,
97}
98
99#[derive(Debug)]
101pub struct Plan {
102 graph: Graph,
104 manifests: PinnedManifests,
106 compilation_order: Vec<NodeIx>,
108}
109
110#[derive(Debug, Error)]
112pub enum PlanError {
113 #[error("failed to fetch the package graph: {0}")]
115 FetchGraph(#[from] FetchGraphError),
116 #[error("{0}")]
118 DependencyCycle(#[from] DependencyCycle),
119}
120
121#[derive(Debug, Error)]
122#[error("failed to fetch graph")]
123pub enum FetchGraphError {
124 #[error("{0}")]
125 MemberName(#[from] MemberNameNotUnique),
126 #[error("failed to source dependency package {0:?}: {1}")]
127 Source(String, source::SourceError),
128 #[error("failed to pin/fetch dependency package {0:?}: {1}")]
129 PinAndFetch(String, Box<source::PinAndFetchError>),
130 #[error("package {0:?}'s dependency named {1:?} failed the depenency manifest check: {2}")]
131 DepManifest(String, String, DepManifestError),
132}
133
134#[derive(Debug, Error)]
136#[error("more than one member package with name {0:?}")]
137pub struct MemberNameNotUnique(pub String);
138
139#[derive(Debug, Error)]
141pub enum DepManifestError {
142 #[error("declared as a {0:?} dependency, but is actually a {1:?}")]
144 UnexpectedKind(DepKind, manifest::PackageKind),
145 #[error(
147 "dependency name {0:?} must match the manifest project name {1:?} \
148 unless `package = {1:?}` is specified in the dependency declaration"
149 )]
150 UnexpectedName(String, String),
151}
152
153#[derive(Debug, Error)]
155#[error("failed to parse `PinnedId`: expected 16-char hex string, found {0:?}")]
156pub struct PinnedIdFromStrError(String);
157
158#[derive(Debug, Error)]
160pub enum InvalidDepCause {
161 #[error("graph contains none of the given members")]
163 NoMembersFound,
164 #[error("{0}")]
165 Check(#[from] CheckDepError),
166}
167
168#[derive(Debug, Error)]
170pub enum CheckDepError {
171 #[error("{0}")]
173 PathError(#[from] DepPathError),
174 #[error("{0}")]
176 DepManifestFileError(#[from] ManifestFileError),
177 #[error("no entry in parent manifest")]
179 NoEntryInManifest,
180 #[error("{0}")]
182 Source(#[from] source::SourceError),
183 #[error("the graph source {0:?} does not match the manifest entry {1:?}")]
185 SourceMismatch(Source, Source),
186 #[error("{0}")]
188 DepManifestError(#[from] DepManifestError),
189}
190
191#[derive(Debug, Error)]
195#[error("failed to find path root: `path` dependency {0:?} has no parent")]
196pub struct FindPathRootError(String);
197
198#[derive(Debug, Error)]
200pub enum CheckPathRootError {
201 #[error("{0}")]
202 NotFound(#[from] FindPathRootError),
203 #[error("invalid path root for dependency {0:?}: expected {1}, found {2}")]
204 Invalid(String, PinnedId, PinnedId),
205}
206
207#[derive(Debug, Error)]
209pub enum DepPathError {
210 #[error("{0}")]
212 Source(#[from] source::DepPathError),
213 #[error("{0}")]
215 CheckPathRoot(#[from] CheckPathRootError),
216 #[error("dependency named {0:?} does not appear in manifest of {1:?}")]
218 NoEntryInManifest(String, String),
219 #[error("supposed member dependency {0:?} not found in member manifests")]
221 MemberNotFound(String),
222 #[error("dependency named {0:?} specified a path {0:?} that does not exist")]
224 PathDoesNotExist(String, PathBuf),
225}
226
227#[derive(Debug, Error)]
229#[error("cycle detected between the following packages: {0:?}")]
230pub struct DependencyCycle(pub Vec<String>);
231
232impl Dep {
233 fn new(name: String, kind: DepKind) -> Self {
234 Self { name, kind }
235 }
236}
237
238impl Pinned {
239 pub fn id(&self) -> PinnedId {
243 PinnedId::new(&self.name, &self.source)
244 }
245
246 pub fn unpinned(&self, path: &Path) -> Pkg {
248 let source = self.source.unpinned(path);
249 let name = self.name.clone();
250 Pkg { name, source }
251 }
252}
253
254impl PinnedId {
255 pub fn new(name: &str, source: &source::Pinned) -> Self {
258 let mut hasher = hash_map::DefaultHasher::default();
259 name.hash(&mut hasher);
260 source.hash(&mut hasher);
261 Self(hasher.finish())
262 }
263}
264
265impl Plan {
266 pub fn graph(&self) -> &Graph {
268 &self.graph
269 }
270
271 pub fn manifests(&self) -> &PinnedManifests {
273 &self.manifests
274 }
275
276 pub fn compilation_order(&self) -> &[NodeIx] {
279 &self.compilation_order
280 }
281}
282
283impl fmt::Display for PinnedId {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 write!(f, "{:016x}", self.0)
286 }
287}
288
289impl str::FromStr for PinnedId {
290 type Err = PinnedIdFromStrError;
291 fn from_str(s: &str) -> Result<Self, Self::Err> {
292 let res = u64::from_str_radix(s, 16);
293 Ok(Self(res.map_err(|_| PinnedIdFromStrError(s.to_string()))?))
294 }
295}
296
297pub fn from_members(members: &MemberManifests) -> Result<Plan, PlanError> {
301 let mut graph = Graph::default();
303 let mut pinned_manifests = PinnedManifests::default();
304 fetch_graph(members, &mut graph, &mut pinned_manifests)?;
305
306 {
308 let (pinned_manifests2, invalid_deps) = check_graph(&graph, members);
309 assert!(invalid_deps.is_empty(), "{invalid_deps:?}");
310 assert_eq!(pinned_manifests, pinned_manifests2);
311 }
312
313 let compilation_order = compilation_order(&graph)?;
316
317 Ok(Plan {
318 graph,
319 manifests: pinned_manifests,
320 compilation_order,
321 })
322}
323
324fn fetch_graph(
326 member_manifests: &MemberManifests,
327 graph: &mut Graph,
328 pinned_manifests: &mut PinnedManifests,
329) -> Result<HashSet<NodeIx>, FetchGraphError> {
330 let mut added_nodes = HashSet::default();
331 for name in member_manifests.keys() {
332 let added = fetch_graph_from_member(member_manifests, name, graph, pinned_manifests)?;
333 added_nodes.extend(added);
334 }
335 Ok(added_nodes)
336}
337
338fn fetch_graph_from_member(
349 member_manifests: &MemberManifests,
350 member_name: &str,
351 graph: &mut Graph,
352 pinned_manifests: &mut PinnedManifests,
353) -> Result<HashSet<NodeIx>, FetchGraphError> {
354 let member_manifest = &member_manifests[member_name];
356
357 let member_node = find_member_node(graph, &member_manifest.pkg.name)?
359 .unwrap_or_else(|| insert_member_pkg(member_manifest.clone(), graph, pinned_manifests));
360
361 let fetch_ts = std::time::Instant::now();
363 let fetch_id = source::fetch_graph_id(member_manifest.dir(), fetch_ts);
364 let path_root = graph[member_node].id();
365
366 let mut fetched = fetched_pkgs(graph, pinned_manifests);
368 let mut visited: HashSet<NodeIx> = HashSet::default();
369
370 let mut ctx = FetchCtx {
371 id: fetch_id,
372 fetched: &mut fetched,
373 visited: &mut visited,
374 };
375
376 fetch_deps(
377 member_manifests,
378 member_node,
379 path_root,
380 graph,
381 pinned_manifests,
382 &mut ctx,
383 )
384}
385
386fn insert_member_pkg(
388 member_manifest: ManifestFile,
389 graph: &mut Graph,
390 pinned_manifests: &mut PinnedManifests,
391) -> NodeIx {
392 let name = member_manifest.pkg.name.clone();
393 let source = source::Pinned::MEMBER;
394 let pkg = Pinned { name, source };
395 let pkg_id = pkg.id();
396 pinned_manifests.insert(pkg_id, member_manifest);
397 graph.add_node(pkg)
398}
399
400fn fetched_pkgs(graph: &Graph, pinned_manifests: &PinnedManifests) -> FetchedPkgs {
405 graph
406 .node_indices()
407 .map(|n| {
408 let pinned = &graph[n];
409 let manifest = &pinned_manifests[&pinned.id()];
410 let pkg = pinned.unpinned(manifest.dir());
411 (pkg, n)
412 })
413 .collect()
414}
415
416fn manifest_deps(
418 manifest: &ManifestFile,
419) -> impl '_ + Iterator<Item = (String, Dependency, DepKind)> {
420 let deps = manifest
421 .deps
422 .iter()
423 .map(|(name, dep)| (name.clone(), dep.clone(), DepKind::Library));
424 let contract_deps = manifest
425 .contract_deps
426 .iter()
427 .map(|(name, dep)| (name.clone(), dep.clone(), DepKind::Contract));
428 deps.chain(contract_deps)
429}
430
431fn fetch_deps(
433 member_manifests: &MemberManifests,
434 node: NodeIx,
435 path_root: PinnedId,
436 graph: &mut Graph,
437 pinned_manifests: &mut PinnedManifests,
438 ctx: &mut FetchCtx,
439) -> Result<HashSet<NodeIx>, FetchGraphError> {
440 let mut added = HashSet::default();
441 let parent_id = graph[node].id();
442 let deps: Vec<_> = manifest_deps(&pinned_manifests[&parent_id]).collect();
443 for (dep_name, dep, dep_kind) in deps {
444 let pkg_name = dep.package.as_ref().unwrap_or(&dep_name);
445 let parent_manifest_dir = pinned_manifests[&parent_id].dir();
446 let source =
447 Source::from_manifest_dep(parent_manifest_dir, &dep, member_manifests.values())
448 .map_err(|e| FetchGraphError::Source(pkg_name.to_string(), e))?;
449
450 let dep_pkg = Pkg {
452 name: pkg_name.to_string(),
453 source,
454 };
455 let dep_node = match ctx.fetched.entry(dep_pkg) {
456 hash_map::Entry::Occupied(entry) => *entry.get(),
457 hash_map::Entry::Vacant(entry) => {
458 let pkg = entry.key();
459 let ctx = source::PinCtx {
460 _fetch_id: ctx.id,
461 path_root,
462 pkg_name: &pkg.name,
463 };
464 let source = pkg
465 .source
466 .pin_and_fetch(ctx, pinned_manifests)
467 .map_err(|e| FetchGraphError::PinAndFetch(pkg.name.clone(), Box::new(e)))?;
468 let name = pkg.name.clone();
469 let dep_pinned = Pinned { name, source };
470 let dep_node = graph.add_node(dep_pinned);
471 added.insert(dep_node);
472 *entry.insert(dep_node)
473 }
474 };
475
476 let dep_edge = Dep::new(dep_name.to_string(), dep_kind.clone());
477 graph.update_edge(node, dep_node, dep_edge.clone());
479
480 if !ctx.visited.insert(dep_node) {
482 continue;
483 }
484
485 let dep_pinned = &graph[dep_node];
487 let dep_pkg_id = dep_pinned.id();
488 check_dep_manifest(dep_pinned, &pinned_manifests[&dep_pkg_id], &dep_edge)
489 .map_err(|e| FetchGraphError::DepManifest(graph[node].name.clone(), dep_name, e))?;
490
491 let path_root = match dep_pinned.source {
492 source::Pinned::Member(_) => dep_pkg_id,
493 source::Pinned::Path(_) => path_root,
494 };
495
496 added.extend(fetch_deps(
498 member_manifests,
499 dep_node,
500 path_root,
501 graph,
502 pinned_manifests,
503 ctx,
504 )?);
505 }
506 Ok(added)
507}
508
509fn find_member_node(g: &Graph, pkg_name: &str) -> Result<Option<NodeIx>, MemberNameNotUnique> {
513 let mut matching = member_nodes_with_name(g, pkg_name);
514 let node_opt = matching.next();
515 match matching.next() {
516 None => Ok(node_opt),
517 Some(_) => Err(MemberNameNotUnique(pkg_name.to_string())),
518 }
519}
520
521fn member_nodes(g: &Graph) -> impl '_ + Iterator<Item = NodeIx> {
523 g.node_indices()
524 .filter(|&n| g[n].source == source::Pinned::MEMBER)
525}
526
527fn member_nodes_with_name<'a>(
529 g: &'a Graph,
530 pkg_name: &'a str,
531) -> impl 'a + Iterator<Item = NodeIx> {
532 member_nodes(g).filter(move |&n| g[n].name == pkg_name)
533}
534
535fn check_graph(graph: &Graph, members: &MemberManifests) -> (PinnedManifests, InvalidDeps) {
540 let mut pinned_manifests = PinnedManifests::default();
542 let mut invalid_deps = InvalidDeps::default();
543 for n in member_nodes(graph) {
544 let pinned = &graph[n];
545 let name = &pinned.name;
546 let Some(manifest) = members.get(name) else {
547 continue;
548 };
549 pinned_manifests.insert(pinned.id(), manifest.clone());
550 invalid_deps.extend(check_deps(graph, members, n, &mut pinned_manifests));
551 }
552
553 if pinned_manifests.is_empty() {
555 invalid_deps.extend(
556 graph
557 .edge_indices()
558 .map(|e| (e, InvalidDepCause::NoMembersFound)),
559 );
560 }
561
562 (pinned_manifests, invalid_deps)
563}
564
565fn check_deps(
570 graph: &Graph,
571 members: &MemberManifests,
572 node: NodeIx,
573 pinned_manifests: &mut PinnedManifests,
574) -> InvalidDeps {
575 let mut remove = InvalidDeps::default();
576 for edge in graph.edges_directed(node, Direction::Outgoing) {
577 let dep_manifest = match check_dep(graph, members, edge, pinned_manifests) {
578 Ok(manifest) => manifest,
579 Err(err) => {
580 remove.insert(edge.id(), InvalidDepCause::Check(err));
581 continue;
582 }
583 };
584 let dep_node = edge.target();
585 let dep_id = graph[dep_node].id();
586 if pinned_manifests.insert(dep_id, dep_manifest).is_none() {
587 let rm = check_deps(graph, members, dep_node, pinned_manifests);
588 remove.extend(rm);
589 }
590 }
591 remove
592}
593
594fn check_dep(
599 graph: &Graph,
600 members: &MemberManifests,
601 edge: EdgeReference,
602 pinned_manifests: &PinnedManifests,
603) -> Result<ManifestFile, CheckDepError> {
604 let node = edge.source();
605 let dep_node = edge.target();
606 let dep = edge.weight();
607 let node_manifest = &pinned_manifests[&graph[node].id()];
608 let dep_path = dep_path(graph, members, node_manifest, dep_node)?;
609 let dep_manifest_path = dep_path.join(ManifestFile::FILE_NAME);
610 let dep_manifest = ManifestFile::from_path(&dep_manifest_path)?;
611 let dep_entry = node_manifest
612 .dep(&dep.name)
613 .ok_or(CheckDepError::NoEntryInManifest)?;
614 let dep_source = Source::from_manifest_dep(node_manifest.dir(), dep_entry, members.values())?;
615 let dep_pkg = graph[dep_node].unpinned(&dep_path);
616 if dep_pkg.source != dep_source {
617 return Err(CheckDepError::SourceMismatch(dep_pkg.source, dep_source));
618 }
619 check_dep_manifest(&graph[dep_node], &dep_manifest, dep)?;
620 Ok(dep_manifest)
621}
622
623fn dep_path(
627 graph: &Graph,
628 members: &MemberManifests,
629 node_manifest: &ManifestFile,
630 dep_node: NodeIx,
631) -> Result<PathBuf, DepPathError> {
632 let dep = &graph[dep_node];
633 let dep_name = &dep.name;
634 match dep.source.dep_path(&dep.name)? {
635 source::DependencyPath::ManifestPath(path) => Ok(path),
636 source::DependencyPath::Member => members
637 .values()
638 .find(|m| m.pkg.name == *dep_name)
639 .map(|m| m.dir().to_path_buf())
640 .ok_or_else(|| DepPathError::MemberNotFound(dep_name.to_string())),
641 source::DependencyPath::Root(path_root) => {
642 check_path_root(graph, dep_node, path_root)?;
643 match node_manifest.dep_path(dep_name) {
645 None => Err(DepPathError::NoEntryInManifest(
646 dep_name.to_string(),
647 node_manifest.pkg.name.to_string(),
648 )),
649 Some(path) if !path.exists() => {
650 Err(DepPathError::PathDoesNotExist(dep_name.to_string(), path))
651 }
652 Some(path) => Ok(path),
653 }
654 }
655 }
656}
657
658fn check_dep_manifest(
660 dep: &Pinned,
661 dep_manifest: &ManifestFile,
662 dep_edge: &Dep,
663) -> Result<(), DepManifestError> {
664 match (&dep_edge.kind, &dep_manifest.pkg.kind) {
666 (DepKind::Contract, manifest::PackageKind::Contract)
667 | (DepKind::Library, manifest::PackageKind::Library) => (),
668 _ => {
669 let pkg_kind = dep_manifest.pkg.kind.clone();
670 return Err(DepManifestError::UnexpectedKind(
671 dep_edge.kind.clone(),
672 pkg_kind,
673 ));
674 }
675 }
676
677 if dep.name != dep_manifest.pkg.name {
679 let pkg_name = dep_manifest.pkg.name.clone();
680 return Err(DepManifestError::UnexpectedName(dep.name.clone(), pkg_name));
681 }
682
683 Ok(())
684}
685
686fn check_path_root(
688 graph: &Graph,
689 path_dep: NodeIx,
690 path_root: PinnedId,
691) -> Result<(), CheckPathRootError> {
692 let path_root_node = find_path_root(graph, path_dep)?;
693 if graph[path_root_node].id() != path_root {
694 return Err(CheckPathRootError::Invalid(
695 graph[path_dep].name.to_string(),
696 path_root,
697 graph[path_root_node].id(),
698 ));
699 }
700 Ok(())
701}
702
703fn find_path_root(graph: &Graph, mut node: NodeIx) -> Result<NodeIx, FindPathRootError> {
707 loop {
708 let pkg = &graph[node];
709 match pkg.source {
710 source::Pinned::Member(_) => return Ok(node),
711 source::Pinned::Path(ref src) => {
712 let parent = graph
713 .edges_directed(node, Direction::Incoming)
714 .next()
715 .map(|edge| edge.source())
716 .ok_or_else(|| FindPathRootError(format!("{src}")))?;
717 node = parent;
718 }
719 }
720 }
721}
722
723fn compilation_order(graph: &Graph) -> Result<Vec<NodeIx>, DependencyCycle> {
727 let rev_graph = petgraph::visit::Reversed(&graph);
728 petgraph::algo::toposort(rev_graph, None).map_err(|_cycle| {
729 let sccs = petgraph::algo::kosaraju_scc(graph);
730 let cycle = sccs
731 .into_iter()
732 .find(|path| path.len() > 1)
733 .expect("one cycle must exist")
734 .into_iter()
735 .map(|n| graph[n].name.to_string())
736 .collect();
737 DependencyCycle(cycle)
738 })
739}