From 5016918a44d8a8f0c27aa17080fe709f05375093 Mon Sep 17 00:00:00 2001 From: MaXFeeD Date: Wed, 24 Sep 2025 19:50:38 +0300 Subject: [PATCH] Remade dependency graph example to test suite, package venv support --- examples/dependency-graph/__main__.py | 52 +++++++++++++++------------ pyproject.toml | 3 ++ src/icmtoolchain/cli.py | 15 ++++---- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/examples/dependency-graph/__main__.py b/examples/dependency-graph/__main__.py index 61b59dc..d6e7c40 100644 --- a/examples/dependency-graph/__main__.py +++ b/examples/dependency-graph/__main__.py @@ -1,28 +1,36 @@ from os.path import basename, dirname, join -from ..cli import (resolve_circular_references, # type: ignore - show_unresolved_dependencies) -from ..config import FileConfig # type: ignore -from ..language import MakeDataConfig # type: ignore -from ..project_graph import ProjectGraph # type: ignore -from ..shell import failure, pretty_debug, success # type: ignore +from icmtoolchain.cli import resolve_circular_references +from icmtoolchain.config import FileConfig +from icmtoolchain.language import MakeDataConfig +from icmtoolchain.project_graph import ProjectGraph +from icmtoolchain.utils import RuntimeCodeError - -def pretty_print_order(graph: ProjectGraph): - return ' -> '.join([basename(edge.project.directory) \ - if isinstance(edge.project, FileConfig) \ - else str(edge) for edge in graph.traverse_dependencies()]) +RESOLVED_DEPENDENCY_ORDER = [ + "8-multi-reference", + "4-circular-dependency", + "7-simple-reference", + "3-cross-reference", + "2-circular-reference", + "6-multi-dependency", + "5-simple-dependency", + "1-master-project" +] master_project_path = join(dirname(__file__), "1-master-project") master_project = MakeDataConfig.of(master_project_path) -if master_project: - graph = ProjectGraph(master_project) - graph.collect_dependencies(master_project) - success(f"Got {len(graph.values())} nodes of {basename(master_project_path)!r} dependencies") - unresolved_artifacts = graph.resolve_dependencies() - show_unresolved_dependencies(unresolved_artifacts) - pretty_debug(f"Currently order (excluding affected projects): {pretty_print_order(graph)}") - resolve_circular_references(graph) - success(f"Order: {pretty_print_order(graph)}") -else: - failure("No such project.") +if not master_project: + raise RuntimeCodeError(1, "No such project.") +graph = ProjectGraph(master_project) +graph.collect_dependencies(master_project) +if len(graph) != len(RESOLVED_DEPENDENCY_ORDER): + raise RuntimeCodeError(2, f"Graph dependencies length mismatch: {len(RESOLVED_DEPENDENCY_ORDER)} != {len(graph)}.") +graph.resolve_dependencies() +if not resolve_circular_references(graph): + raise RuntimeCodeError(3, "Expected circular dependencies, but nothing was found.") +resolved_dependencies = [] +for dependency in graph.traverse_dependencies(): + assert isinstance(dependency.project, FileConfig) + resolved_dependencies.append(basename(dependency.project.directory)) +if resolved_dependencies != RESOLVED_DEPENDENCY_ORDER: + raise RuntimeCodeError(4, f"Traversed dependencies sorting mismatch: {' -> '.join(resolved_dependencies)}.") diff --git a/pyproject.toml b/pyproject.toml index a35848a..cb2965d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,3 +45,6 @@ build-backend = "pdm.backend" [tool.pdm] distribution = true + +[tool.pdm.build] +package-dir = "src" diff --git a/src/icmtoolchain/cli.py b/src/icmtoolchain/cli.py index 52a626a..a74b160 100644 --- a/src/icmtoolchain/cli.py +++ b/src/icmtoolchain/cli.py @@ -70,8 +70,7 @@ def resolve_circular_references(graph: ProjectGraph) -> bool: if any(unused_edges): repr_unused_edges = [f"{edge}" for edge in unused_edges] failure(f"Following dependencies have been removed as there is no further connection in them to other projects: {', '.join(repr_unused_edges)}!") - from .utils import RuntimeCodeError - raise RuntimeCodeError(255, "Cannot build a project with unresolved dependencies!") + raise RuntimeError("Cannot build a project with unresolved dependencies!") return True def execute_task(callable: 'NamedCallable') -> None: @@ -355,20 +354,20 @@ def run_cli_test(): def run_example_test(name: str): examples_directory = join(dirname(__file__), "..", "..", "examples") if not isdir(examples_directory): - failure("Examples directory is not available.") - return + raise RuntimeError("Examples directory is not available.") example_executable = join(examples_directory, name, "__main__.py") if not isfile(example_executable): - failure(f"No such example {name!r}. It should be one of: {', '.join(listdir(examples_directory))}.") - return + raise RuntimeError(f"No such example {name!r}. It should be one of: {', '.join(listdir(examples_directory))}.") from importlib.util import module_from_spec, spec_from_file_location example_name = f"{__name__[:__name__.rindex('.')]}.examples.{name}" example_spec = spec_from_file_location(example_name, example_executable) - assert example_spec + if not example_spec: + raise RuntimeError(f"Cannot obtain example {name!r} spec.") example_module = module_from_spec(example_spec) sys.modules[example_name] = example_module - assert example_spec.loader + if not example_spec.loader: + raise RuntimeError(f"Cannot obtain example {name!r} module loader.") example_spec.loader.exec_module(example_module) if __name__ == "__main__":