diff --git a/scarb-metadata/src/lib.rs b/scarb-metadata/src/lib.rs index 343c4eec3..7412bb8db 100644 --- a/scarb-metadata/src/lib.rs +++ b/scarb-metadata/src/lib.rs @@ -37,7 +37,7 @@ mod command; mod version_pin; /// An "opaque" identifier for a package. -/// It is possible to inspect the `repr` field, if the need arises, +/// It is possible to inspect the `repr` field if the need arises, /// but its precise format is an implementation detail and is subject to change. /// /// [`Metadata`] can be indexed by [`PackageId`]. @@ -61,7 +61,7 @@ impl fmt::Display for PackageId { } /// An "opaque" identifier for a source. -/// It is possible to inspect the `repr` field, if the need arises, +/// It is possible to inspect the `repr` field if the need arises, /// but its precise format is an implementation detail and is subject to change. #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] @@ -83,7 +83,7 @@ impl fmt::Display for SourceId { } /// An "opaque" identifier for a compilation unit. -/// It is possible to inspect the `repr` field, if the need arises, +/// It is possible to inspect the `repr` field if the need arises, /// but its precise format is an implementation detail and is subject to change. /// /// [`Metadata`] can be indexed by [`CompilationUnitId`]. @@ -106,6 +106,30 @@ impl fmt::Display for CompilationUnitId { } } +/// An "opaque" identifier for a compilation unit component. +/// It is possible to inspect the `repr` field if the need arises, +/// but its precise format is an implementation detail and is subject to change. +/// +/// [`CompilationUnitMetadata`] can be indexed by [`CompilationUnitComponentId`]. +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] +pub struct CompilationUnitComponentId { + /// The underlying string representation of the ID. + pub repr: String, +} + +impl From for CompilationUnitComponentId { + fn from(repr: String) -> Self { + Self { repr } + } +} + +impl fmt::Display for CompilationUnitComponentId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.repr, f) + } +} + fn current_profile_default() -> String { "release".to_string() } @@ -356,6 +380,28 @@ pub struct CompilationUnitComponentMetadata { /// If not specified, the one from `CompilationUnit` will be used. #[serde(default)] pub cfg: Option>, + /// Identifier of this component. It is unique in its compilation unit. + pub id: Option, + /// Dependencies of this component. + pub dependencies: Option>, + + /// Additional data not captured by deserializer. + #[cfg_attr(feature = "builder", builder(default))] + #[serde(flatten)] + pub extra: HashMap, +} + +/// Information about dependency of a component of a compilation unit. +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "builder", derive(Builder))] +#[cfg_attr(feature = "builder", builder(setter(into)))] +#[non_exhaustive] +pub struct CompilationUnitComponentDependencyMetadata { + /// Id of a component from the same compilation unit that this dependency refers to. + /// + /// This should directly translate to a `discriminator` field in Cairo compiler terminology, + /// except that it should be `None` for `core` crate **only**. + pub id: CompilationUnitComponentId, /// Additional data not captured by deserializer. #[cfg_attr(feature = "builder", builder(default))] @@ -527,3 +573,14 @@ impl CompilationUnitComponentMetadata { .expect("Source path is guaranteed to point to a file.") } } + +impl<'a> Index<&'a CompilationUnitComponentId> for CompilationUnitMetadata { + type Output = CompilationUnitComponentMetadata; + + fn index(&self, idx: &'a CompilationUnitComponentId) -> &Self::Output { + self.components + .iter() + .find(|p| p.id.as_ref() == Some(idx)) + .unwrap_or_else(|| panic!("no compilation unit with this ID: {idx}")) + } +} diff --git a/scarb/src/compiler/compilation_unit.rs b/scarb/src/compiler/compilation_unit.rs index cdd20a171..ea4831040 100644 --- a/scarb/src/compiler/compilation_unit.rs +++ b/scarb/src/compiler/compilation_unit.rs @@ -75,12 +75,16 @@ pub struct ProcMacroCompilationUnit { #[derive(Clone, Debug)] #[non_exhaustive] pub struct CompilationUnitComponent { + /// Unique id identifying this component. + pub id: CompilationUnitComponentId, /// The Scarb [`Package`] to be built. pub package: Package, /// Information about the specific target to build, out of the possible targets in `package`. pub targets: Vec, /// Items for the Cairo's `#[cfg(...)]` attribute to be enabled in this component. pub cfg_set: Option, + /// Dependencies of this component. + pub dependencies: Vec, } /// Information about a single package that is a compiler plugin to load for [`CompilationUnit`]. @@ -92,6 +96,20 @@ pub struct CompilationUnitCairoPlugin { pub builtin: bool, } +/// Unique identifier of the compilation unit component. +/// Currently, a compilation unit can be uniquely identified by [`PackageId`] only. +/// It may be not sufficient in the future depending on changes to the compilation model. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct CompilationUnitComponentId { + pub package_id: PackageId, +} + +impl CompilationUnitComponentId { + pub fn to_discriminator(&self) -> String { + self.package_id.to_serialized_string() + } +} + pub trait CompilationUnitAttributes { fn main_package_id(&self) -> PackageId; fn components(&self) -> &[CompilationUnitComponent]; @@ -288,9 +306,13 @@ impl CompilationUnitComponent { ); } Ok(Self { + id: CompilationUnitComponentId { + package_id: package.id, + }, package, targets, cfg_set, + dependencies: vec![], }) } diff --git a/scarb/src/compiler/db.rs b/scarb/src/compiler/db.rs index e88d17c39..0e675ceba 100644 --- a/scarb/src/compiler/db.rs +++ b/scarb/src/compiler/db.rs @@ -17,7 +17,7 @@ use tracing::trace; use crate::compiler::plugin::proc_macro::{ProcMacroHost, ProcMacroHostPlugin}; use crate::compiler::{CairoCompilationUnit, CompilationUnitAttributes, CompilationUnitComponent}; -use crate::core::{ManifestDependency, TestTargetProps, TestTargetType, Workspace}; +use crate::core::Workspace; use crate::DEFAULT_MODULE_MAIN_FILE; pub struct ScarbDatabase { @@ -145,66 +145,25 @@ fn build_project_config(unit: &CairoCompilationUnit) -> Result { .map(|component| { let experimental_features = component.package.manifest.experimental_features.clone(); let experimental_features = experimental_features.unwrap_or_default(); - // Those are direct dependencies of the component. - let dependencies_summary: Vec<&ManifestDependency> = component - .package - .manifest - .summary - .full_dependencies() - .collect(); - // We iterate over all of the compilation unit components to get dependency's version. - let mut dependencies: BTreeMap = unit - .components + let dependencies: BTreeMap = component + .dependencies .iter() - .filter(|component_as_dependency| { - dependencies_summary.iter().any(|dependency_summary| { - dependency_summary.name == component_as_dependency.package.id.name - }) || - // This is a hacky way of accommodating integration test components, - // which need to depend on the tested package. - component_as_dependency - .package - .manifest - .targets - .iter() - .filter(|target| target.kind.is_test()) - .any(|target| { - target.group_id.clone().unwrap_or(target.name.clone()) - == component.package.id.name.to_smol_str() - && component_as_dependency.cairo_package_name() != component.cairo_package_name() - }) - }) - .map(|compilation_unit_component| { + .map(|compilation_unit_component_id| { + let compilation_unit_component = unit.components.iter().find(|component| component.id == *compilation_unit_component_id) + .expect("Dependency of a component is guaranteed to exist in compilation unit components"); ( compilation_unit_component.package.id.name.to_string(), DependencySettings { discriminator: (compilation_unit_component.package.id.name.to_string() != *CORELIB_CRATE_NAME) - .then_some(compilation_unit_component.package.id.version.clone()) - .map(|v| v.to_smolstr()), + .then_some(compilation_unit_component.id.clone()) + .map(|v| v.to_discriminator().to_smolstr()), }, ) }) .collect(); - // Adds itself to dependencies - let is_integration_test = if component.first_target().kind.is_test() { - let props: Option = component.first_target().props().ok(); - props - .map(|props| props.test_type == TestTargetType::Integration) - .unwrap_or_default() - } else { false }; - if !is_integration_test { - dependencies.insert( - component.package.id.name.to_string(), - DependencySettings { - discriminator: (component.package.id.name.to_string() != *CORELIB_CRATE_NAME) - .then_some(component.package.id.version.clone()).map(|v| v.to_smolstr()), - }, - ); - } - ( component.cairo_package_name(), CrateSettings { diff --git a/scarb/src/ops/metadata.rs b/scarb/src/ops/metadata.rs index ce0a18d7c..6dbb55433 100644 --- a/scarb/src/ops/metadata.rs +++ b/scarb/src/ops/metadata.rs @@ -10,7 +10,7 @@ use scarb_ui::args::PackagesSource; use crate::compiler::{ CairoCompilationUnit, CompilationUnit, CompilationUnitAttributes, CompilationUnitComponent, - ProcMacroCompilationUnit, + CompilationUnitComponentId, ProcMacroCompilationUnit, }; use crate::core::{ edition_variant, DepKind, DependencyVersionReq, ManifestDependency, Package, PackageId, @@ -330,6 +330,19 @@ where .expect("Cairo's `Cfg` must serialize identically as Scarb Metadata's `Cfg`.") }) .collect::>())) + .id(wrap_compilation_unit_component_id(&c.id)) + .dependencies( + Some( + c.dependencies + .iter() + .map(|component_id| + m::CompilationUnitComponentDependencyMetadataBuilder::default() + .id(component_id.to_discriminator()) + .build(). + unwrap() + ).collect() + ) + ) .build() .unwrap() }) @@ -386,6 +399,14 @@ fn wrap_source_id(id: SourceId) -> m::SourceId { } } +fn wrap_compilation_unit_component_id( + id: &CompilationUnitComponentId, +) -> m::CompilationUnitComponentId { + m::CompilationUnitComponentId { + repr: id.to_discriminator(), + } +} + fn btree_toml_to_json(map: &BTreeMap) -> BTreeMap { map.iter() .map(|(k, v)| (k.to_string(), toml_to_json(v))) diff --git a/scarb/src/ops/resolve.rs b/scarb/src/ops/resolve.rs index 6a4f3620a..22b6f18f9 100644 --- a/scarb/src/ops/resolve.rs +++ b/scarb/src/ops/resolve.rs @@ -1,7 +1,7 @@ use crate::compiler::plugin::{fetch_cairo_plugin, CairoPluginProps}; use crate::compiler::{ CairoCompilationUnit, CompilationUnit, CompilationUnitAttributes, CompilationUnitCairoPlugin, - CompilationUnitComponent, ProcMacroCompilationUnit, Profile, + CompilationUnitComponent, CompilationUnitComponentId, ProcMacroCompilationUnit, Profile, }; use crate::core::lockfile::Lockfile; use crate::core::package::{Package, PackageClass, PackageId}; @@ -26,6 +26,7 @@ use futures::TryFutureExt; use indoc::formatdoc; use itertools::Itertools; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::iter::zip; pub struct WorkspaceResolve { pub resolve: Resolve, @@ -390,6 +391,61 @@ fn cairo_compilation_unit_for_target( member.id }; + // Collect dependencies for the components. + let dependencies_for_components: Vec<_> = components + .iter() + .map(|component| { + // Those are direct dependencies of the component. + let dependencies_summary: Vec<&ManifestDependency> = component + .package + .manifest + .summary + .full_dependencies() + .collect(); + + // We iterate over all the compilation unit components to get dependency's version. + let mut dependencies: Vec = components + .iter() + .filter(|component_as_dependency| { + dependencies_summary.iter().any(|dependency_summary| { + dependency_summary.name == component_as_dependency.package.id.name + }) || + // This is a hacky way of accommodating integration test components, + // which need to depend on the tested package. + component_as_dependency + .package + .manifest + .targets + .iter() + .filter(|target| target.kind.is_test()) + .any(|target| { + target.group_id.clone().unwrap_or(target.name.clone()) + == component.package.id.name.to_smol_str() + && component_as_dependency.cairo_package_name() != component.cairo_package_name() + }) + }) + .map(|compilation_unit_component| compilation_unit_component.id.clone() + ) + .collect(); + + // Adds itself to dependencies + let is_integration_test = if component.first_target().kind.is_test() { + let props: Option = component.first_target().props().ok(); + props + .map(|props| props.test_type == TestTargetType::Integration) + .unwrap_or_default() + } else { false }; + if !is_integration_test { + dependencies.push(component.id.clone()); + } + + dependencies + }).collect(); + + for (component, dependencies) in zip(&mut components, dependencies_for_components) { + component.dependencies = dependencies; + } + Ok(CairoCompilationUnit { main_package_id, components,