From 1b9b0f68c376aca5c9de40d3426524d1f1cd14d2 Mon Sep 17 00:00:00 2001 From: AGiantSquid Date: Mon, 16 Oct 2023 00:46:27 -0700 Subject: [PATCH] fix: add recursion checks to prevent dependency loops (#10) * fix: add recursion checks to prevent dependency loops - the dependency resolver is recursive which allow for detecting transitive dependencies - the recursive dependency search now keeps an accumulator of all seen dependencies, to prevent duplicate entries in case multiple packages depend on the same local package - a check is also added to prevent a package from listing itself as a dependency, which would cause an infinite recursive loop * Update src/monas/project.py --------- Co-authored-by: Frost Ming --- src/monas/project.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/monas/project.py b/src/monas/project.py index bc85e20..491e4ce 100644 --- a/src/monas/project.py +++ b/src/monas/project.py @@ -3,7 +3,7 @@ import textwrap from pathlib import Path from shlex import join as sh_join -from typing import Type, cast +from typing import Optional, Type, cast import tomlkit from packaging.utils import canonicalize_name @@ -155,18 +155,33 @@ def remove_dependency(self, dependency: str) -> None: def install(self) -> None: """Bootstrap the package and link depending packages in the monorepo""" - local_dependencies = [*self.get_local_dependencies(), self] + local_dependencies = self.get_local_dependencies([self]) requirements = [ sh_join(["-e", pkg.path.as_posix()]) for pkg in local_dependencies ] pip_install(self.path / ".venv", requirements) - def get_local_dependencies(self) -> list[PyPackage]: - """Return list of local dependencies.""" + def get_local_dependencies( + self, + local_dependencies: list[PyPackage] | None = None, + ) -> list[PyPackage]: + """Return list of local dependencies. + + Args: + local_dependencies: Accumulated list of local depencies to install + """ + if not local_dependencies: + local_dependencies = [] dependency_names = self.metadata.get_dependency_names() - local_dependencies = [] local_packages = list(self.config.iter_packages()) for pkg in local_packages: - if pkg.canonical_name in dependency_names: - local_dependencies += [*pkg.get_local_dependencies(), pkg] + pkg_name = pkg.canonical_name + if pkg_name not in dependency_names: + continue + if pkg_name == self.canonical_name: + raise ValueError(f'{self.name} cannot have a dependency on itself') + if pkg_name in [ld.canonical_name for ld in local_dependencies]: + continue + local_dependencies = [pkg, *local_dependencies] + local_dependencies = pkg.get_local_dependencies(local_dependencies) return local_dependencies