Documentation · Static metrics
km deps
Dependency graph analysis
Analyzes internal module dependencies by parsing import/use/require statements. Builds a directed graph of file-level coupling and detects cycles using Tarjan’s SCC algorithm.
km deps [path]
Supports Elixir (every module referred to in the code, with alias undone; see the notes below), Rust (every path that names a module of the workspace: use, crate::, super::, a child module; see the notes below), Python (import and from … import, absolute and relative; see the notes below), JavaScript/TypeScript (relative import/require), Go (imports matching the module path from go.mod), and Kaikai (import a.b.c, including the as and .{…} forms). External dependencies (crates, npm packages, the Kaikai stdlib) are ignored.
Files in any other language are left out of the graph instead of being listed with zero dependencies. The table footer, the unsupported array of the JSON output and the unsupported:N field of the short format say how many files were skipped, so “not measured” is never shown as “no dependencies”.
Elixir notes:
- Dependencies are between modules, and most need no import, so every module name in the code counts as a reference: calls, structs,
use,import,require,@behaviour,defimpl. Comments, strings, heredocs and sigils are set aside first, so a doctest is not a dependency. aliasis undone, includingas:, grouped and multi-line forms, and__MODULE__. The statement itself is not a use.- Nested
defmodules are named after the module that contains them, told by indentation asmix formatleaves it. - A module defined in several files (projects made from one template) resolves to the file nearest the one referring to it.
- Comments and literals hold no references, but the code a string interpolates does:
"Total: #{Orders.total(order)}"usesOrders. - A Phoenix controller uses the views named after it (
PageControllerandPageJSON,PageHTML,PageView), and a module uses the components its~Htemplates render. - Not seen: modules named at run time (
apply/3, configuration), those generated by macros, and those a Phoenix router names under the alias of ascope.
Rust notes:
- A dependency is a path:
use crate::git::GitRepo, a qualifiedsuper::analyzer::run(x), a call on a child module (report::print(x)). Groups,as,self, globs and multi-lineuseare read.mod x;only says where a module lives and adds no edge. - Each crate is a tree of modules grown from its root (
src/lib.rs,src/main.rs, a file ofsrc/bin,tests,examplesorbenches,build.rs) by itsmoddeclarations,#[path]included. A path leads to the file of the deepest module it names: the item may be defined there or only re-exported. - Other targets of a package reach its library by name (
my_app::ordersfromtests/ormain.rs), and so do the other crates of the workspace. The name comes from[package] name, with-read as_. - A type uses the files that hold its
implblocks, since what they define is reached through the type. - For
km impact, a file that holds#[test]functions has a test of its own; a file oftests/that holds them, or atests.rsmodule, is a test. What a package runs (src/main.rs,src/bin,examples,benches,build.rs) is an entry point. - Comments, strings and
$cratein a macro name nothing. - Not seen: what a macro generates or names,
include!, a library with a[lib] pathornameof its own, a dependency renamed inCargo.toml, and modules behindcfg, which all count. A use only from an inline test module still makes the file a dependent. A test that runs the binary (assert_cmd,CARGO_BIN_EXE_*) names no file, so it protects none. Two packages of the same name in one repository are not told apart.
Python notes:
- Every
import a.bandfrom a.b import cis read, wherever it is written: in a function, underif TYPE_CHECKING:, over several lines in parentheses or with a backslash. One written in a docstring or a comment is not an import. - A dotted path names
a/b.pyor the packagea/b/__init__.py; a stub (.pyi) stands for a module with no source. Only the deepest module is used:import a.b.cadds no edge toa/__init__.py. - In
from a.b import c,cis the submodulea/b/c.pywhen that file exists, and a namea.bdefines otherwise. - A relative import starts at the package of the importing file, one level up per extra dot.
- An absolute import is looked up from each directory above the importing file that is not itself a package (it holds no
__init__.py), nearest first, and from thesrcdirectory under each. That covers a project run from its root, asrclayout withtests/beside it, and several projects in one repository. Then come the roots the nearestpyproject.tomlabove declares: where the build backend finds the packages (whereandpackage-dirof setuptools,packagesof Poetry,sourcesandpackagesof Hatch,python-sourceof maturin,package-dirof PDM,module-rootof uv) and what pytest adds (pythonpath). What is found under none of them is external: the standard library, installed packages. - For
km impact,test_*.py,*_test.pyandtests.pyare tests, andconftest.pyis test support, which protects nothing;__main__.py,setup.pyandmanage.pyare entry points, and so is what sits undermanagement/commands. - Not seen: modules named at run time (
importlib,__import__, Django settings andINSTALLED_APPS), roots added at run time (sys.path,PYTHONPATH, a.pthfile) or declared insetup.py,setup.cfgorpytest.ini, and a namespace package spread over several roots. A name a package re-exports leads to its__init__.py, and from there to the module that defines it.
Kaikai notes:
import a.b.cnamesa/b/c.kairelative to a package root, not to the importing file. Each ancestor directory of the importing file is tried, nearest first, so runkm depson a directory that contains the package root.- When no such file exists,
import acan name a package directorya/that carries akai.toml; the import then depends on every.kaifile directly inside it. - An import that resolves to no analysed file is external and adds no edge.
- The files of one Kaikai package merge, so a module can use a name declared in another file of its package without importing it. The import graph is therefore a lower bound on the real dependencies.
| Flag | Description |
|---|---|
--format {table,json,short,terse} | Output format (default: table) |
--cycles-only | Show only files that participate in a dependency cycle |
--sort-by METRIC | Sort by fan-out (default) or fan-in |
--top N | Show only top N files (default: 20) |
Example output:
Dependency Graph
────────────────────────────────────────────────────────────────────────
File Language Fan-In Fan-Out Cycle
────────────────────────────────────────────────────────────────────────
report_helpers.rs Rust 26 1 no
util.rs Rust 25 2 yes
walk.rs Rust 24 1 no
────────────────────────────────────────────────────────────────────────
Dependency cycles: 14
Cycle 1 (3 files):
cogcom/analyzer.rs
cogcom/detection.rs
cogcom/report.rs