Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: checks for cyclic dependencies #3699

Merged
merged 9 commits into from
Jan 3, 2024
3 changes: 3 additions & 0 deletions tooling/nargo_toml/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pub enum ManifestError {

#[error(transparent)]
SemverError(SemverError),

#[error("Cyclic package dependency found when processing {toml}")]
CyclicDependency { toml: PathBuf },
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}

#[allow(clippy::enum_variant_names)]
Expand Down
48 changes: 36 additions & 12 deletions tooling/nargo_toml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]

use std::{
collections::BTreeMap,
collections::{BTreeMap, HashSet},
path::{Component, Path, PathBuf},
};

Expand Down Expand Up @@ -99,7 +99,11 @@ struct PackageConfig {
}

impl PackageConfig {
fn resolve_to_package(&self, root_dir: &Path) -> Result<Package, ManifestError> {
fn resolve_to_package(
&self,
root_dir: &Path,
processed: &mut HashSet<String>,
) -> Result<Package, ManifestError> {
let name: CrateName = if let Some(name) = &self.package.name {
name.parse().map_err(|_| ManifestError::InvalidPackageName {
toml: root_dir.join("Nargo.toml"),
Expand All @@ -115,7 +119,7 @@ impl PackageConfig {
toml: root_dir.join("Nargo.toml"),
name: name.into(),
})?;
let resolved_dep = dep_config.resolve_to_dependency(root_dir)?;
let resolved_dep = dep_config.resolve_to_dependency(root_dir, processed)?;

dependencies.insert(name, resolved_dep);
}
Expand Down Expand Up @@ -263,7 +267,11 @@ enum DependencyConfig {
}

impl DependencyConfig {
fn resolve_to_dependency(&self, pkg_root: &Path) -> Result<Dependency, ManifestError> {
fn resolve_to_dependency(
&self,
pkg_root: &Path,
processed: &mut HashSet<String>,
) -> Result<Dependency, ManifestError> {
let dep = match self {
Self::Github { git, tag, directory } => {
let dir_path = clone_git_repo(git, tag).map_err(ManifestError::GitError)?;
Expand All @@ -280,13 +288,13 @@ impl DependencyConfig {
dir_path
};
let toml_path = project_path.join("Nargo.toml");
let package = resolve_package_from_toml(&toml_path)?;
let package = resolve_package_from_toml(&toml_path, processed)?;
Dependency::Remote { package }
}
Self::Path { path } => {
let dir_path = pkg_root.join(path);
let toml_path = dir_path.join("Nargo.toml");
let package = resolve_package_from_toml(&toml_path)?;
let package = resolve_package_from_toml(&toml_path, processed)?;
Dependency::Local { package }
}
};
Expand All @@ -305,9 +313,10 @@ fn toml_to_workspace(
nargo_toml: NargoToml,
package_selection: PackageSelection,
) -> Result<Workspace, ManifestError> {
let mut resolved = HashSet::new();
let workspace = match nargo_toml.config {
Config::Package { package_config } => {
let member = package_config.resolve_to_package(&nargo_toml.root_dir)?;
let member = package_config.resolve_to_package(&nargo_toml.root_dir, &mut resolved)?;
match &package_selection {
PackageSelection::Selected(selected_name) if selected_name != &member.name => {
return Err(ManifestError::MissingSelectedPackage(member.name))
Expand All @@ -325,7 +334,7 @@ fn toml_to_workspace(
for (index, member_path) in workspace_config.members.into_iter().enumerate() {
let package_root_dir = nargo_toml.root_dir.join(&member_path);
let package_toml_path = package_root_dir.join("Nargo.toml");
let member = resolve_package_from_toml(&package_toml_path)?;
let member = resolve_package_from_toml(&package_toml_path, &mut resolved)?;

match &package_selection {
PackageSelection::Selected(selected_name) => {
Expand Down Expand Up @@ -382,17 +391,32 @@ fn read_toml(toml_path: &Path) -> Result<NargoToml, ManifestError> {
}

/// Resolves a Nargo.toml file into a `Package` struct as defined by our `nargo` core.
fn resolve_package_from_toml(toml_path: &Path) -> Result<Package, ManifestError> {
fn resolve_package_from_toml(
toml_path: &Path,
processed: &mut HashSet<String>,
) -> Result<Package, ManifestError> {
// Checks for cyclic dependencies
let str_path = toml_path.to_str().expect("ICE - path is empty");
if processed.contains(str_path) {
return Err(ManifestError::CyclicDependency { toml: toml_path.to_path_buf() });
}
// Adds the package to the set of resolved packages
if let Some(str) = toml_path.to_str() {
processed.insert(str.to_string());
}

let nargo_toml = read_toml(toml_path)?;

match nargo_toml.config {
let result = match nargo_toml.config {
Config::Package { package_config } => {
package_config.resolve_to_package(&nargo_toml.root_dir)
package_config.resolve_to_package(&nargo_toml.root_dir, processed)
}
Config::Workspace { .. } => {
Err(ManifestError::UnexpectedWorkspace(toml_path.to_path_buf()))
}
}
};
processed.remove(str_path);
result
}

#[derive(Debug, PartialEq, Eq)]
Expand Down
Loading