diff --git a/crates/assembly/src/assembler.rs b/crates/assembly/src/assembler.rs index 9bb4e7b5ee..01d01e9388 100644 --- a/crates/assembly/src/assembler.rs +++ b/crates/assembly/src/assembler.rs @@ -349,8 +349,7 @@ impl Assembler { TargetType::Kernel => { if !self.kernel().is_empty() { return Err(Report::msg(format!( - "duplicate kernels present in the dependency graph: '{}@{ - }' conflicts with another kernel we've already linked", + "duplicate kernels present in the dependency graph: '{}@{}' conflicts with another kernel we've already linked", &package.name, &package.version ))); } diff --git a/crates/assembly/src/project.rs b/crates/assembly/src/project.rs index 0736771370..48af8e45ec 100644 --- a/crates/assembly/src/project.rs +++ b/crates/assembly/src/project.rs @@ -13,8 +13,8 @@ use miden_core::serde::{Deserializable, Serializable}; use miden_mast_package::{Package as MastPackage, Section, SectionId, TargetType}; use miden_package_registry::{PackageId, PackageStore, Version as PackageVersion}; use miden_project::{ - Linkage, Package as ProjectPackage, Profile, ProjectDependencyNodeProvenance, ProjectSource, - ProjectSourceOrigin, Target, + Linkage, Package as ProjectPackage, PreassembledDependencyMetadata, Profile, + ProjectDependencyNodeProvenance, ProjectSource, ProjectSourceOrigin, Target, }; use crate::{Assembler, assembler::debuginfo::DebugInfoSections, ast::Module}; @@ -411,8 +411,19 @@ where package, } }, - ProjectDependencyNodeProvenance::Preassembled { path, selected } => { - let package = load_selected_preassembled_package(path, package_id, selected)?; + ProjectDependencyNodeProvenance::Preassembled { + path, + selected, + kind, + requirements, + } => { + let package = load_selected_preassembled_package( + path, + package_id, + selected, + *kind, + requirements, + )?; ResolvedPackage { linked_kernel_package: self.resolve_linked_kernel_package(package.clone())?, package, @@ -694,6 +705,8 @@ fn load_selected_preassembled_package( path: &FsPath, expected_name: &PackageId, selected: &PackageVersion, + expected_kind: TargetType, + expected_requirements: &BTreeMap, ) -> Result, Report> { let package = load_package_from_path(path)?; if &package.name != expected_name { @@ -716,6 +729,26 @@ fn load_selected_preassembled_package( ))); } + if package.kind != expected_kind { + return Err(Report::msg(format!( + "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph target kind '{}'", + expected_name, + actual, + path.display(), + expected_kind + ))); + } + + let actual_requirements = package_requirements(&package); + if &actual_requirements != expected_requirements { + return Err(Report::msg(format!( + "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph dependency requirements", + expected_name, + actual, + path.display() + ))); + } + Ok(package) } @@ -727,3 +760,21 @@ fn load_package_from_path(path: &FsPath) -> Result, Report> { })?; Ok(Arc::new(package)) } + +fn package_requirements( + package: &MastPackage, +) -> BTreeMap { + package + .manifest + .dependencies() + .map(|dependency| { + ( + dependency.name.clone(), + PreassembledDependencyMetadata { + version: PackageVersion::new(dependency.version.clone(), dependency.digest), + kind: dependency.kind, + }, + ) + }) + .collect() +} diff --git a/crates/assembly/src/project/tests.rs b/crates/assembly/src/project/tests.rs index 454faa5c1f..5d9626f3e0 100644 --- a/crates/assembly/src/project/tests.rs +++ b/crates/assembly/src/project/tests.rs @@ -813,7 +813,9 @@ end let error = context .assemble_library_package(&root_manifest, None) .expect_err("runtime dependency digest conflicts should fail"); - assert!(error.to_string().contains("conflicting runtime dependency 'runtime'")); + let error = error.to_string(); + assert!(error.contains("dependency resolution failed")); + assert!(error.contains("there is no version of runtime")); } #[test] @@ -1942,7 +1944,7 @@ fn preassembled_libraries_prefer_store_kernel_over_embedded_copy() { } #[test] -fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_is_missing() { +fn preassembled_libraries_require_registered_kernel_when_store_is_missing() { let tempdir = TempDir::new().unwrap(); let (_, kernel_manifest) = write_transitive_kernel_program_project(tempdir.path()); let mid_manifest = tempdir.path().join("mid").join("miden-project.toml"); @@ -1952,11 +1954,6 @@ fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_is_missing() { let kernel_package = build_context .assemble_library_package(&kernel_manifest, None) .expect("kernel package build should succeed"); - let expected_kernel = kernel_package - .try_into_kernel_library() - .expect("kernel package should round-trip as a kernel library") - .kernel() - .clone(); let mut mid_package = MastPackage::read_from_bytes( &build_context .assemble_library_package(&mid_manifest, None) @@ -1972,23 +1969,11 @@ fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_is_missing() { let root_manifest = write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path); let mut context = TestContext::new(); - let package = context + let error = context .assemble_executable_package(&root_manifest, Some("main"), None) - .expect("executable package build should fall back to the embedded kernel"); - let embedded_kernel_package = package - .sections - .iter() - .find(|section| section.id == SectionId::KERNEL) - .map(|section| MastPackage::read_from_bytes(section.data.as_ref()).unwrap()) - .expect("executable package should embed the fallback kernel package"); - assert_eq!(embedded_kernel_package.version, kernel_package.version); - assert_eq!(embedded_kernel_package.digest(), kernel_package.digest()); - - let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes()) - .expect("serialized executable package should round-trip") - .try_into_program() - .expect("program reconstruction should use the embedded fallback kernel"); - assert_eq!(round_tripped_program.kernel(), &expected_kernel); + .expect_err("executable package build should reject unresolved kernel dependencies"); + assert!(error.to_string().contains("dependency resolution failed")); + assert!(error.to_string().contains("kernelpkg")); } #[test] @@ -2057,7 +2042,7 @@ fn preassembled_libraries_fall_back_to_embedded_kernel_when_store_artifact_is_un } #[test] -fn preassembled_libraries_without_store_or_embedded_kernel_leave_runtime_kernel_to_caller() { +fn preassembled_libraries_without_store_or_embedded_kernel_cannot_reconstruct_program() { let tempdir = TempDir::new().unwrap(); write_transitive_kernel_program_project(tempdir.path()); let mid_manifest = tempdir.path().join("mid").join("miden-project.toml"); @@ -2073,23 +2058,11 @@ fn preassembled_libraries_without_store_or_embedded_kernel_leave_runtime_kernel_ let root_manifest = write_preassembled_kernel_executable_project(tempdir.path(), &mid_package_path); let mut context = TestContext::new(); - let package = context + let error = context .assemble_executable_package(&root_manifest, Some("main"), None) - .expect("executable package build should succeed without an available kernel artifact"); - - assert!( - package - .manifest - .dependencies() - .any(|dependency| dependency.kind == TargetType::Kernel) - ); - assert!(!package.sections.iter().any(|section| section.id == SectionId::KERNEL)); - - let round_tripped_program = MastPackage::read_from_bytes(&package.to_bytes()) - .expect("serialized executable package should round-trip") - .try_into_program() - .expect("packages without a recoverable kernel should still convert to a program"); - assert!(round_tripped_program.kernel().is_empty()); + .expect_err("packages with unresolved kernel runtime dependencies must be rejected"); + assert!(error.to_string().contains("dependency resolution failed")); + assert!(error.to_string().contains("kernelpkg")); } #[test] @@ -2129,7 +2102,7 @@ fn embedded_kernel_package_must_match_runtime_dependency() { } #[test] -fn executable_packages_without_embedded_kernel_section_fall_back_to_empty_kernel() { +fn executable_packages_without_embedded_kernel_section_are_rejected() { let tempdir = TempDir::new().unwrap(); let manifest_path = write_kernel_program_project(tempdir.path()); @@ -2141,11 +2114,10 @@ fn executable_packages_without_embedded_kernel_section_fall_back_to_empty_kernel .expect("serialized executable package should round-trip"); round_tripped_package.sections.retain(|section| section.id != SectionId::KERNEL); - let round_tripped_program = round_tripped_package + let error = round_tripped_package .try_into_program() - .expect("packages without embedded kernels should still convert to a program"); - - assert!(round_tripped_program.kernel().is_empty()); + .expect_err("packages without embedded kernels should be rejected"); + assert!(error.to_string().contains("does not embed the kernel package required")); } #[test] @@ -2191,6 +2163,230 @@ end assert!(error.to_string().contains("no longer matches the dependency graph selection")); } +#[test] +fn preassembled_dependency_must_match_graph_selected_runtime_dependencies() { + let tempdir = TempDir::new().unwrap(); + let runtime_v1 = Arc::::from(MastPackage::generate( + "runtime".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + [], + )); + let runtime_v2 = Arc::::from(MastPackage::generate( + "runtime".into(), + "1.0.1".parse().unwrap(), + TargetType::Library, + [], + )); + let dep_package_path = tempdir.path().join("dep.masp"); + let dep_v1 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + runtime_v1.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime_v1.version.clone(), + kind: TargetType::Library, + digest: runtime_v1.digest(), + }], + ); + dep_v1.write_to_file(&dep_package_path).unwrap(); + + let root_dir = tempdir.path().join("root"); + let root_manifest = root_dir.join("miden-project.toml"); + write_file( + &root_manifest, + r#"[package] +name = "root" +version = "1.0.0" + +[lib] +path = "lib.masm" + +[dependencies] +dep = { path = "../dep.masp" } +"#, + ); + write_file( + &root_dir.join("lib.masm"), + r#"pub proc entry + exec.::dep::foo +end +"#, + ); + + let mut context = TestContext::new(); + context.registry_mut().add_package(runtime_v1.clone()); + let mut project_assembler = context.project_assembler_for_path(&root_manifest).unwrap(); + let dep_v2 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + runtime_v1.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime_v2.version.clone(), + kind: TargetType::Library, + digest: runtime_v2.digest(), + }], + ); + dep_v2.write_to_file(&dep_package_path).unwrap(); + + let error = project_assembler.assemble(ProjectTargetSelector::Library, "dev").expect_err( + "changing preassembled dependency metadata after graph construction should fail", + ); + assert!( + error + .to_string() + .contains("no longer matches the dependency graph dependency requirements") + ); +} + +#[test] +fn preassembled_dependency_must_match_graph_selected_dependency_kinds() { + let tempdir = TempDir::new().unwrap(); + let runtime = Arc::::from(MastPackage::generate( + "runtime".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + [], + )); + let dep_package_path = tempdir.path().join("dep.masp"); + let dep_v1 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + runtime.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime.version.clone(), + kind: TargetType::Library, + digest: runtime.digest(), + }], + ); + dep_v1.write_to_file(&dep_package_path).unwrap(); + + let root_dir = tempdir.path().join("root"); + let root_manifest = root_dir.join("miden-project.toml"); + write_file( + &root_manifest, + r#"[package] +name = "root" +version = "1.0.0" + +[lib] +path = "lib.masm" + +[dependencies] +dep = { path = "../dep.masp" } +"#, + ); + write_file( + &root_dir.join("lib.masm"), + r#"pub proc entry + exec.::dep::foo +end +"#, + ); + + let mut context = TestContext::new(); + context.registry_mut().add_package(runtime.clone()); + let mut project_assembler = context.project_assembler_for_path(&root_manifest).unwrap(); + let dep_v2 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + runtime.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime.version.clone(), + kind: TargetType::Kernel, + digest: runtime.digest(), + }], + ); + dep_v2.write_to_file(&dep_package_path).unwrap(); + + let error = project_assembler + .assemble(ProjectTargetSelector::Library, "dev") + .expect_err("changing preassembled dependency kinds after graph construction should fail"); + assert!( + error + .to_string() + .contains("no longer matches the dependency graph dependency requirements") + ); +} + +#[test] +fn preassembled_package_must_match_graph_selected_target_kind() { + let tempdir = TempDir::new().unwrap(); + let runtime = Arc::::from(MastPackage::generate( + "runtime".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + [], + )); + let dep_package_path = tempdir.path().join("dep.masp"); + let dep_v1 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + runtime.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime.version.clone(), + kind: TargetType::Library, + digest: runtime.digest(), + }], + ); + dep_v1.write_to_file(&dep_package_path).unwrap(); + + let root_dir = tempdir.path().join("root"); + let root_manifest = root_dir.join("miden-project.toml"); + write_file( + &root_manifest, + r#"[package] +name = "root" +version = "1.0.0" + +[lib] +path = "lib.masm" + +[dependencies] +dep = { path = "../dep.masp" } +"#, + ); + write_file( + &root_dir.join("lib.masm"), + r#"pub proc entry + exec.::dep::foo +end +"#, + ); + + let mut context = TestContext::new(); + context.registry_mut().add_package(runtime.clone()); + let mut project_assembler = context.project_assembler_for_path(&root_manifest).unwrap(); + let dep_v2 = MastPackage::from_library( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Kernel, + runtime.mast.clone(), + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime.version.clone(), + kind: TargetType::Library, + digest: runtime.digest(), + }], + ); + dep_v2.write_to_file(&dep_package_path).unwrap(); + + let error = project_assembler + .assemble(ProjectTargetSelector::Library, "dev") + .expect_err("changing preassembled package kind after graph construction should fail"); + assert!(error.to_string().contains("no longer matches the dependency graph target kind")); +} + fn write_kernel_program_project(root: &FsPath) -> PathBuf { let manifest_path = root.join("miden-project.toml"); write_file( diff --git a/crates/assembly/tests/fixtures/protocol/kernel/bin/main-alt.masm b/crates/assembly/tests/fixtures/protocol/kernel/bin/main-alt.masm index 411644105d..d4e0538b91 100644 --- a/crates/assembly/tests/fixtures/protocol/kernel/bin/main-alt.masm +++ b/crates/assembly/tests/fixtures/protocol/kernel/bin/main-alt.masm @@ -8,9 +8,6 @@ proc invoke_user_script end begin - # At program start, initialize the kernel environment - exec.$kernel::init - # Execute the user script whose entrypoint digest is on top of the stack exec.invoke_user_script end diff --git a/crates/assembly/tests/fixtures/protocol/kernel/bin/main.masm b/crates/assembly/tests/fixtures/protocol/kernel/bin/main.masm index 411644105d..d4e0538b91 100644 --- a/crates/assembly/tests/fixtures/protocol/kernel/bin/main.masm +++ b/crates/assembly/tests/fixtures/protocol/kernel/bin/main.masm @@ -8,9 +8,6 @@ proc invoke_user_script end begin - # At program start, initialize the kernel environment - exec.$kernel::init - # Execute the user script whose entrypoint digest is on top of the stack exec.invoke_user_script end diff --git a/crates/assembly/tests/fixtures/protocol/kernel/miden-project.toml b/crates/assembly/tests/fixtures/protocol/kernel/miden-project.toml index e87e320759..dd3513a1e1 100644 --- a/crates/assembly/tests/fixtures/protocol/kernel/miden-project.toml +++ b/crates/assembly/tests/fixtures/protocol/kernel/miden-project.toml @@ -15,5 +15,4 @@ name = "entry-alt" path = "bin/main-alt.masm" [dependencies] -miden-core.workspace = true miden-utils.workspace = true diff --git a/crates/assembly/tests/fixtures/protocol/miden-project.toml b/crates/assembly/tests/fixtures/protocol/miden-project.toml index dba8b90c5b..d27e7d6f08 100644 --- a/crates/assembly/tests/fixtures/protocol/miden-project.toml +++ b/crates/assembly/tests/fixtures/protocol/miden-project.toml @@ -6,7 +6,6 @@ members = [ ] [workspace.dependencies] -miden-core = { path = "../../../../../lib/core/asm" } miden-utils = { path = "utils", linkage = "static" } miden-tx = { path = "kernel" } diff --git a/crates/assembly/tests/fixtures/protocol/userspace/miden-project.toml b/crates/assembly/tests/fixtures/protocol/userspace/miden-project.toml index a294dab6db..0e25d34893 100644 --- a/crates/assembly/tests/fixtures/protocol/userspace/miden-project.toml +++ b/crates/assembly/tests/fixtures/protocol/userspace/miden-project.toml @@ -7,6 +7,5 @@ namespace = "miden::protocol" path = "mod.masm" [dependencies] -miden-core.workspace = true miden-utils = { workspace = true, linkage = "dynamic" } miden-tx.workspace = true diff --git a/crates/mast-package/src/package/mod.rs b/crates/mast-package/src/package/mod.rs index 76112502ef..a246215052 100644 --- a/crates/mast-package/src/package/mod.rs +++ b/crates/mast-package/src/package/mod.rs @@ -179,13 +179,21 @@ impl Package { && let Some(entrypoint) = self.mast.mast_forest().find_procedure_root(digest) { let mast_forest = self.mast.mast_forest().clone(); - match self.try_embedded_kernel_library()? { - Some(kernel_library) => Ok(Program::with_kernel( + let kernel_dependency = self.kernel_runtime_dependency()?.cloned(); + match (self.try_embedded_kernel_library()?, kernel_dependency) { + (Some(kernel_library), _) => Ok(Program::with_kernel( mast_forest, entrypoint, kernel_library.kernel().clone(), )), - None => Ok(Program::new(mast_forest, entrypoint)), + (None, Some(kernel_dependency)) => Err(Report::msg(format!( + "package '{}' declares kernel runtime dependency '{}@{}#{}', but does not embed the kernel package required to reconstruct a program", + self.name, + kernel_dependency.name, + kernel_dependency.version, + kernel_dependency.digest + ))), + (None, None) => Ok(Program::new(mast_forest, entrypoint)), } } else { Err(Report::msg(format!( diff --git a/crates/project/src/dependencies/graph.rs b/crates/project/src/dependencies/graph.rs index b47f3980f6..5752931ae6 100644 --- a/crates/project/src/dependencies/graph.rs +++ b/crates/project/src/dependencies/graph.rs @@ -125,9 +125,20 @@ pub enum ProjectDependencyNodeProvenance { path: PathBuf, /// The version of the preassembled package selected: Version, + /// The target kind of the preassembled package + kind: crate::TargetType, + /// The dependency requirements declared by the preassembled package manifest + requirements: BTreeMap, }, } +/// The manifest dependency metadata pinned for a preassembled package. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PreassembledDependencyMetadata { + pub version: Version, + pub kind: crate::TargetType, +} + /// Represents information about a package whose provenance is a Miden project in source form. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ProjectSource { @@ -473,16 +484,9 @@ impl<'a, R: PackageRegistry + ?Sized> ProjectDependencyGraphBuilder<'a, R> { continue; } - let Some(versions) = self.registry.available_versions(&package) else { - continue; - }; - - for record in versions.values() { - registry - .insert_record(package.clone(), record.clone()) - .map_err(|error| Report::msg(error.to_string()))?; - - for dependency in record.dependencies().keys() { + if let Some(versions) = registry.available_versions(&package) { + for dependency in versions.values().flat_map(|record| record.dependencies().keys()) + { if local_packages.contains(dependency) { return Err(Report::msg(format!( "dependency conflict for '{dependency}': local source or preassembled dependency conflicts with a registry dependency" @@ -492,6 +496,26 @@ impl<'a, R: PackageRegistry + ?Sized> ProjectDependencyGraphBuilder<'a, R> { pending.insert(dependency.clone()); } } + continue; + } + + if let Some(versions) = self.registry.available_versions(&package) { + for record in versions.values() { + registry + .insert_record(package.clone(), record.clone()) + .map_err(|error| Report::msg(error.to_string()))?; + + for dependency in record.dependencies().keys() { + if local_packages.contains(dependency) { + return Err(Report::msg(format!( + "dependency conflict for '{dependency}': local source or preassembled dependency conflicts with a registry dependency" + ))); + } + if !copied.contains(dependency) { + pending.insert(dependency.clone()); + } + } + } } } @@ -765,17 +789,37 @@ impl<'a, R: PackageRegistry + ?Sized> ProjectDependencyGraphBuilder<'a, R> { self.ensure_version_satisfies(expected_name, requirement, selected.clone())?; } + let mut dependencies = Vec::with_capacity(package.manifest.num_dependencies()); + let requirements = package_requirements(&package); + let mut solver_dependencies = BTreeMap::new(); + for dependency in package.manifest.dependencies() { + solver_dependencies.insert( + dependency.name.clone(), + VersionRequirement::Exact(Version::new( + dependency.version.clone(), + dependency.digest, + )), + ); + + dependencies.push(ProjectDependencyEdge { + dependency: dependency.name.clone(), + linkage: Linkage::Dynamic, + }); + } + Ok(CollectedDependencyNode { graph_node: ProjectDependencyNode { - dependencies: Vec::new(), + dependencies, name: PackageId::from(expected_name), provenance: ProjectDependencyNodeProvenance::Preassembled { path, selected: selected.clone(), + kind: package.kind, + requirements, }, version: selected.version.clone(), }, - solver_dependencies: BTreeMap::new(), + solver_dependencies, }) } @@ -1031,6 +1075,24 @@ struct GitCheckout { resolved_revision: Arc, } +fn package_requirements( + package: &MastPackage, +) -> BTreeMap { + package + .manifest + .dependencies() + .map(|dependency| { + ( + dependency.name.clone(), + PreassembledDependencyMetadata { + version: Version::new(dependency.version.clone(), dependency.digest), + kind: dependency.kind, + }, + ) + }) + .collect() +} + #[cfg(test)] mod tests { use alloc::{boxed::Box, string::ToString}; @@ -1600,11 +1662,122 @@ mod tests { ProjectDependencyNodeProvenance::Preassembled { ref path, ref selected, + .. } if path == &package_path.canonicalize().unwrap() && *selected == Version::new("1.0.0".parse().unwrap(), digest) ); } + #[test] + fn preassembled_path_dependency_propagates_runtime_dependencies_into_resolution() { + let tempdir = TempDir::new().unwrap(); + let runtime_package = build_registry_test_package("runtime", "1.0.0"); + let runtime_version = + Version::new(runtime_package.version.clone(), runtime_package.digest()); + let dep_package = MastPackage::generate( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + [miden_mast_package::Dependency { + name: PackageId::from("runtime"), + version: runtime_version.version.clone(), + kind: TargetType::Library, + digest: runtime_package.digest(), + }], + ); + let dep_package_path = tempdir.path().join("dep.masp"); + fs::write(&dep_package_path, dep_package.to_bytes()).unwrap(); + + let root_dir = tempdir.path().join("root"); + let root_manifest = write_package( + &root_dir, + "root", + "1.0.0", + Some("export.foo\nend\n"), + [Dependency::new( + Span::unknown("dep".into()), + DependencyVersionScheme::Path { + path: Span::unknown(Uri::from(dep_package_path.as_path())), + version: None, + }, + Linkage::Dynamic, + )], + ); + + let mut registry = TestRegistry::default(); + registry + .insert_record( + PackageId::from("runtime"), + PackageRecord::new(runtime_version.clone(), std::iter::empty()), + ) + .unwrap(); + + let graph = builder(®istry, &tempdir.path().join("git")) + .build_from_path(&root_manifest) + .unwrap(); + let dep = graph.get(&PackageId::from("dep")).unwrap(); + let runtime = graph.get(&PackageId::from("runtime")).unwrap(); + + assert_eq!( + dep.dependencies, + vec![ProjectDependencyEdge { + dependency: PackageId::from("runtime"), + linkage: Linkage::Dynamic, + }] + ); + assert_matches!( + runtime.provenance, + ProjectDependencyNodeProvenance::Registry { ref selected, .. } + if *selected == runtime_version + ); + } + + #[test] + fn preassembled_path_dependency_keeps_embedded_kernel_in_solver_requirements() { + let tempdir = TempDir::new().unwrap(); + let kernel_package = MastPackage::generate( + "kernelpkg".into(), + "1.0.0".parse().unwrap(), + TargetType::Kernel, + [], + ); + let kernel_version = Version::new(kernel_package.version.clone(), kernel_package.digest()); + let dep_package = MastPackage::generate( + "dep".into(), + "1.0.0".parse().unwrap(), + TargetType::Library, + [miden_mast_package::Dependency { + name: PackageId::from("kernelpkg"), + version: kernel_version.version.clone(), + kind: TargetType::Kernel, + digest: kernel_package.digest(), + }], + ); + + let dep_package_path = tempdir.path().join("dep.masp"); + fs::write(&dep_package_path, dep_package.to_bytes()).unwrap(); + + let registry = TestRegistry::default(); + let node = builder(®istry, &tempdir.path().join("git")) + .load_preassembled_dependency(&dep_package_path, "dep", None) + .unwrap(); + + assert_eq!( + node.graph_node.dependencies, + vec![ProjectDependencyEdge { + dependency: PackageId::from("kernelpkg"), + linkage: Linkage::Dynamic, + }] + ); + assert_eq!( + node.solver_dependencies, + BTreeMap::from([( + PackageId::from("kernelpkg"), + VersionRequirement::Exact(kernel_version), + )]) + ); + } + #[test] fn preassembled_path_dependency_validates_digest_requirement_against_artifact_digest() { let tempdir = TempDir::new().unwrap();