Skip to content
This repository was archived by the owner on May 4, 2024. It is now read-only.

Commit 2dcbbe6

Browse files
awelcamnn
andauthored
[move-package] Support overlapping resolution graphs (#929)
Co-authored-by: Ashok Menon <[email protected]>
1 parent 2997843 commit 2dcbbe6

File tree

20 files changed

+465
-35
lines changed

20 files changed

+465
-35
lines changed

language/tools/move-package/src/resolution/dependency_graph.rs

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub struct Package {
6969
resolver: Option<Symbol>,
7070
}
7171

72-
#[derive(Debug, Clone)]
72+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
7373
pub struct Dependency {
7474
pub mode: DependencyMode,
7575
pub subst: Option<PM::Substitution>,
@@ -83,6 +83,16 @@ pub enum DependencyMode {
8383
DevOnly,
8484
}
8585

86+
/// Keeps information about external resolution request
87+
#[derive(Debug, Clone)]
88+
pub struct ExternalRequest {
89+
mode: DependencyMode,
90+
from: Symbol,
91+
to: Symbol,
92+
resolver: Symbol,
93+
pkg_path: PathBuf,
94+
}
95+
8696
/// Wrapper struct to display a package as an inline table in the lock file (matching the
8797
/// convention in the source manifest). This is necessary becase the `toml` crate does not
8898
/// currently support serializing types as inline tables.
@@ -115,13 +125,16 @@ impl DependencyGraph {
115125

116126
// Ensure there's always a root node, even if it has no edges.
117127
graph.package_graph.add_node(graph.root_package);
118-
128+
// Collect external resolution requests and process them later to check for "safe"
129+
// overlapping packages existing in both externally and internally resolved graphs.
130+
let mut external_requests = vec![];
119131
graph
120132
.extend_graph(
121133
&PM::DependencyKind::default(),
122134
root_package,
123135
&root_path,
124136
dependency_cache,
137+
&mut external_requests,
125138
progress_output,
126139
)
127140
.with_context(|| {
@@ -131,6 +144,24 @@ impl DependencyGraph {
131144
)
132145
})?;
133146

147+
for ExternalRequest {
148+
mode,
149+
from,
150+
to,
151+
resolver,
152+
pkg_path,
153+
} in external_requests
154+
{
155+
graph
156+
.resolve_externally(mode, from, to, resolver, &pkg_path, progress_output)
157+
.with_context(|| {
158+
format!(
159+
"Failed to resolve dependencies for package '{}'",
160+
graph.root_package
161+
)
162+
})?
163+
}
164+
134165
graph.check_acyclic()?;
135166
graph.discover_always_deps();
136167

@@ -402,16 +433,18 @@ impl DependencyGraph {
402433
entry.insert(ext_pkg);
403434
}
404435

405-
// Seeing the same package in `extension` as in `self`: Not OK, even if their
406-
// sources match, because supporting this case requires handling complicated edge
407-
// cases (confirming that the sub-graph rooted at this package is also the same).
408-
Entry::Occupied(entry) => {
409-
bail!(
410-
"Conflicting dependencies found:\n{0} = {1}\n{0} = {2}",
411-
ext_name,
412-
PackageWithResolverTOML(entry.get()),
413-
PackageWithResolverTOML(&ext_pkg),
414-
);
436+
// Seeing the same package in `extension` is OK only if it has the same set of
437+
// dependencies as the existing one.i
438+
Entry::Occupied(_) => {
439+
let (self_deps, ext_deps) =
440+
pkg_deps_equal(ext_name, &self.package_graph, &ext_graph);
441+
if self_deps != ext_deps {
442+
bail!(
443+
"Conflicting dependencies found for '{ext_name}' during external resolution by '{resolver}':\n{}{}",
444+
format_deps("\nExternal dependencies not found:", self_deps),
445+
format_deps("\nNew external dependencies:", ext_deps),
446+
);
447+
}
415448
}
416449
}
417450
}
@@ -459,42 +492,41 @@ impl DependencyGraph {
459492
package: &PM::SourceManifest,
460493
package_path: &Path,
461494
dependency_cache: &mut DependencyCache,
495+
external_requests: &mut Vec<ExternalRequest>,
462496
progress_output: &mut Progress,
463497
) -> Result<()> {
464498
let from = package.package.name;
465499
for (to, dep) in &package.dependencies {
466500
match dep {
467-
PM::Dependency::External(resolver) => self.resolve_externally(
468-
DependencyMode::Always,
501+
PM::Dependency::External(resolver) => external_requests.push(ExternalRequest {
502+
mode: DependencyMode::Always,
469503
from,
470-
*to,
471-
*resolver,
472-
package_path,
473-
progress_output,
474-
)?,
475-
504+
to: *to,
505+
resolver: *resolver,
506+
pkg_path: package_path.to_path_buf(),
507+
}),
476508
PM::Dependency::Internal(dep) => self.resolve_internally(
477509
DependencyMode::Always,
478510
from,
479511
*to,
480512
parent,
481513
dep.clone(),
482514
dependency_cache,
515+
external_requests,
483516
progress_output,
484517
)?,
485518
}
486519
}
487520

488521
for (to, dep) in &package.dev_dependencies {
489522
match dep {
490-
PM::Dependency::External(resolver) => self.resolve_externally(
491-
DependencyMode::DevOnly,
523+
PM::Dependency::External(resolver) => external_requests.push(ExternalRequest {
524+
mode: DependencyMode::DevOnly,
492525
from,
493-
*to,
494-
*resolver,
495-
package_path,
496-
progress_output,
497-
)?,
526+
to: *to,
527+
resolver: *resolver,
528+
pkg_path: package_path.to_path_buf(),
529+
}),
498530

499531
PM::Dependency::Internal(dep) => self.resolve_internally(
500532
DependencyMode::DevOnly,
@@ -503,6 +535,7 @@ impl DependencyGraph {
503535
parent,
504536
dep.clone(),
505537
dependency_cache,
538+
external_requests,
506539
progress_output,
507540
)?,
508541
}
@@ -602,6 +635,7 @@ impl DependencyGraph {
602635
parent: &PM::DependencyKind,
603636
dep: PM::InternalDependency,
604637
dependency_cache: &mut DependencyCache,
638+
external_requests: &mut Vec<ExternalRequest>,
605639
progress_output: &mut Progress,
606640
) -> Result<()> {
607641
let PM::InternalDependency {
@@ -618,7 +652,13 @@ impl DependencyGraph {
618652
};
619653

620654
pkg.kind.reroot(parent)?;
621-
self.process_dependency(pkg, to, dependency_cache, progress_output)?;
655+
self.process_dependency(
656+
pkg,
657+
to,
658+
dependency_cache,
659+
external_requests,
660+
progress_output,
661+
)?;
622662
self.package_graph.add_edge(
623663
from,
624664
to,
@@ -641,6 +681,7 @@ impl DependencyGraph {
641681
pkg: Package,
642682
name: PM::PackageName,
643683
dependency_cache: &mut DependencyCache,
684+
external_requests: &mut Vec<ExternalRequest>,
644685
progress_output: &mut Progress,
645686
) -> Result<()> {
646687
let pkg = match self.package_table.entry(name) {
@@ -676,6 +717,7 @@ impl DependencyGraph {
676717
&manifest,
677718
&pkg_path,
678719
dependency_cache,
720+
external_requests,
679721
progress_output,
680722
)
681723
.with_context(|| format!("Resolving dependencies for package '{}'", name))
@@ -904,3 +946,35 @@ fn str_escape(s: &str) -> Result<String, fmt::Error> {
904946
fn path_escape(p: &Path) -> Result<String, fmt::Error> {
905947
str_escape(p.to_str().ok_or(fmt::Error)?)
906948
}
949+
950+
fn format_deps(msg: &str, dependencies: Vec<(&Dependency, PM::PackageName)>) -> String {
951+
let mut s = "".to_string();
952+
if !dependencies.is_empty() {
953+
s.push_str(msg);
954+
for (dep, pkg) in dependencies {
955+
s.push_str("\n\t");
956+
s.push_str(format!("{}", DependencyTOML(pkg, dep)).as_str());
957+
}
958+
}
959+
s
960+
}
961+
962+
/// Checks if dependencies of a given package in two different dependency graph maps are the
963+
/// same.
964+
fn pkg_deps_equal<'a>(
965+
pkg_name: Symbol,
966+
pkg_graph: &'a DiGraphMap<PM::PackageName, Dependency>,
967+
other_graph: &'a DiGraphMap<PM::PackageName, Dependency>,
968+
) -> (
969+
Vec<(&'a Dependency, PM::PackageName)>,
970+
Vec<(&'a Dependency, PM::PackageName)>,
971+
) {
972+
let pkg_edges = BTreeSet::from_iter(pkg_graph.edges(pkg_name).map(|(_, pkg, dep)| (dep, pkg)));
973+
let other_edges =
974+
BTreeSet::from_iter(other_graph.edges(pkg_name).map(|(_, pkg, dep)| (dep, pkg)));
975+
976+
let (pkg_deps, other_deps): (Vec<_>, Vec<_>) = pkg_edges
977+
.symmetric_difference(&other_edges)
978+
.partition(|dep| pkg_edges.contains(dep));
979+
(pkg_deps, other_deps)
980+
}

language/tools/move-package/src/source_package/parsed_manifest.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub struct BuildInfo {
9494
pub architecture: Option<Architecture>,
9595
}
9696

97-
#[derive(Debug, Clone, Eq, PartialEq)]
97+
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
9898
pub enum SubstOrRename {
9999
RenameFrom(NamedAddress),
100100
Assign(AccountAddress),

language/tools/move-package/tests/test_dependency_graph.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,32 @@ fn merge_overlapping() {
293293
)
294294
.expect("Reading inner");
295295

296+
assert!(outer.merge(inner, Symbol::from("")).is_ok());
297+
}
298+
299+
#[test]
300+
fn merge_overlapping_different_deps() {
301+
let tmp = tempfile::tempdir().unwrap();
302+
let mut outer = DependencyGraph::read_from_lock(
303+
tmp.path().to_path_buf(),
304+
Symbol::from("Root"),
305+
&mut A_DEP_B_LOCK.as_bytes(),
306+
)
307+
.expect("Reading outer");
308+
309+
// Test only -- clear always deps because usually `merge` is used while the graph is being
310+
// built, not after it has been entirely read.
311+
outer.always_deps.clear();
312+
313+
let inner = DependencyGraph::read_from_lock(
314+
tmp.path().to_path_buf(),
315+
Symbol::from("B"),
316+
&mut A_LOCK.as_bytes(),
317+
)
318+
.expect("Reading inner");
319+
296320
let Err(err) = outer.merge(inner, Symbol::from("")) else {
297-
panic!("Outer mentions package A, and so does inner.");
321+
panic!("Outer and inner mention package A which has different dependencies in both.");
298322
};
299323

300324
assert_error_contains!(err, "Conflicting dependencies found");
@@ -426,3 +450,22 @@ source = { local = "./A" }
426450
name = "B"
427451
source = { local = "./B" }
428452
"#;
453+
454+
const A_DEP_B_LOCK: &str = r#"
455+
[move]
456+
version = 0
457+
dependencies = [
458+
{ name = "A" },
459+
]
460+
461+
[[move.package]]
462+
name = "A"
463+
source = { local = "./A" }
464+
dependencies = [
465+
{ name = "B" },
466+
]
467+
468+
[[move.package]]
469+
name = "B"
470+
source = { local = "./B" }
471+
"#;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# @generated by Move, please check-in and do not edit manually.
2+
3+
[move]
4+
version = 0
5+
6+
dependencies = [
7+
{ name = "A" },
8+
{ name = "ADep" },
9+
]
10+
11+
[[move.package]]
12+
name = "A"
13+
source = { local = "./deps_only/A" }
14+
15+
dependencies = [
16+
{ name = "ADep" },
17+
]
18+
19+
[[move.package]]
20+
name = "ADep"
21+
source = { local = "deps_only/ADep" }

0 commit comments

Comments
 (0)