Skip to main content

Dependency analyzer for Godot/GDScript projects

Project description

gdcruiser

Static dependency analyzer for Godot projects, inspired by dependency-cruiser. Parses GDScript (.gd) and scene (.tscn) files to build a full dependency graph, detect circular dependencies, enforce architectural rules, and export the results as text, JSON, GraphViz DOT, or Mermaid diagrams. Designed to run locally or in CI pipelines.

Features

  • Parses .gd, .tscn, and .tres files — extends, preload(), load(), class_name, scene/resource scripts, and class-name expressions in code (var x: Foo, obj is Foo, Foo.STATIC_CONST)
  • Detects circular dependencies
  • Resolves class_name declarations to map symbolic inheritance
  • Configurable architectural rules (forbidden, allowed, required, circular, orphan) via .gdcruiser.json or pyproject.toml
  • Multiple output formats: human-readable text, JSON, GraphViz DOT, and Mermaid
  • --exclude flag to filter paths from analysis
  • Non-zero exit code on cycles or rule violations (CI-friendly)

Installation

Requires Python 3.13+.

pip install gdcruiser

Or with uv:

uv tool install gdcruiser

Usage

gdcruiser [-h] [-f {text,json,dot,mermaid}] [-o FILE] [--no-cycles] [-v] [path]
Option Description
path Godot project path (default: current directory)
-f, --format Output format: text (default), json, dot, or mermaid
-o, --output Write output to file instead of stdout
--no-cycles Skip cycle detection
--config FILE Path to config file (.gdcruiser.json or pyproject.toml)
--validate-config Validate config file and exit
--ignore-rules Skip rule evaluation
--exclude PATTERN Regex pattern to exclude paths (can be repeated)
-v, --verbose Verbose output

Examples

Analyze the current directory:

gdcruiser .

Analyze a specific project and output JSON:

gdcruiser /path/to/godot/project -f json

Generate a GraphViz DOT file:

gdcruiser . -f dot -o deps.dot
dot -Tpng deps.dot -o deps.png

Generate a Mermaid diagram:

gdcruiser . -f mermaid

Output Formats

Text (default)

============================================================
GDScript Dependency Analysis
============================================================

Modules: 9
Dependencies: 8

----------------------------------------
CIRCULAR DEPENDENCIES (1 found)
----------------------------------------

Cycle 1:
  -> res://cycle_b.gd
  -> res://cycle_a.gd
  -> res://cycle_b.gd (back to start)

----------------------------------------
MODULE DEPENDENCIES
----------------------------------------

res://player.gd
  class_name: Player
  extends_class: res://base_entity.gd:2
  preload: res://inventory.gd:5

JSON

{
  "graph": {
    "modules": {
      "res://player.gd": {
        "path": "res://player.gd",
        "class_name": "Player",
        "dependencies": [
          {
            "target": "res://base_entity.gd",
            "type": "extends_class",
            "line": 2,
            "resolved": true
          }
        ]
      }
    },
    "stats": {
      "module_count": 9,
      "dependency_count": 8
    }
  },
  "cycles": [],
  "symbols": {
    "Player": "res://player.gd"
  },
  "errors": []
}

GraphViz DOT

digraph dependencies {
    rankdir=LR;
    node [shape=box, fontname="monospace"];

    "res://player.gd" [label="Player\nplayer.gd"];
    "res://base_entity.gd" [label="BaseEntity\nbase_entity.gd"];

    "res://player.gd" -> "res://base_entity.gd" [label="extends"];
}

Nodes involved in cycles are highlighted in red.

Mermaid

Produces a Mermaid flowchart that renders natively on GitHub, GitLab, Notion, and other Markdown tools — no external tooling required.

gdcruiser tests/fixtures -f mermaid
graph LR
    res_base_entity_gd["BaseEntity<br/>base_entity.gd"]
    res_config_gd["config.gd"]
    res_cycle_a_gd["CycleA<br/>cycle_a.gd"]
    res_cycle_b_gd["CycleB<br/>cycle_b.gd"]
    res_enemy_gd["Enemy<br/>enemy.gd"]
    res_game_manager_gd["game_manager.gd"]
    res_inventory_gd["Inventory<br/>inventory.gd"]
    res_player_gd["Player<br/>player.gd"]
    res_player_tscn["player.tscn"]

    res_cycle_a_gd == preload ==> res_cycle_b_gd
    res_cycle_b_gd == preload ==> res_cycle_a_gd
    res_enemy_gd -- extends --> res_base_entity_gd
    res_game_manager_gd -- preload --> res_player_tscn
    res_game_manager_gd -- load --> res_config_gd
    res_player_gd -- extends --> res_base_entity_gd
    res_player_gd -- preload --> res_inventory_gd
    res_player_tscn -- script --> res_player_gd

    classDef cycle fill:#ffcccc,stroke:#cc0000
    class res_cycle_a_gd,res_cycle_b_gd cycle

Cycle nodes are highlighted with a red fill, and cycle edges use thick arrows.

Supported Dependency Patterns

gdcruiser detects the following GDScript patterns:

Pattern Example
extends (path) extends "res://path/to/script.gd"
extends (class) extends ClassName
class_name class_name MyClass
preload() preload("res://path/to/file.gd")
load() load("res://path/to/file.gd")
Typed annotation var x: ClassName, func f(c: ClassName) -> ClassName:
Generic parameter Array[ClassName], Dictionary[String, ClassName]
Type check / cast obj is ClassName, obj as ClassName
Static / constant access ClassName.method(), ClassName.CONSTANT

Class-name references (the last four rows) are resolved against class_name declarations in the project. Built-in Godot types (Node, Vector2, Resource, …) and unresolved identifiers are filtered out so the graph stays clean.

For .tscn files, it detects scripts attached to nodes via [ext_resource].

For .tres files, it detects:

  • script_class="ClassName" in the [gd_resource] header (registers the resource under that class name in the symbol table).
  • Every [ext_resource path="res://..."] reference — to scripts, sub-resources, or scenes — so architectural rules can constrain the data layer.

Custom Rules

gdcruiser can enforce architectural rules on your dependency graph. Rules are defined in .gdcruiser.json (or pyproject.toml under [tool.gdcruiser]) and evaluated with every run unless --ignore-rules is passed.

Configuration file

gdcruiser looks for config files in this order:

  1. --config <path> (explicit)
  2. .gdcruiser.json in the project root
  3. gdcruiser.json in the project root
  4. [tool.gdcruiser] section in pyproject.toml

Rule types

Type Meaning
forbidden Dependency must not exist — a violation is raised when a matching edge is found
allowed Only listed dependencies are permitted — anything else from a matching source is a violation
required Dependency must exist — a violation is raised when a matching source has no edge to the target

Rule fields

Field Type Description
name string Rule identifier shown in violation messages
severity "error" | "warn" | "info" | "ignore" Severity level (default: "error")
comment string Human-readable explanation
from.path regex Match source modules whose path matches
from.pathNot regex Exclude source modules whose path matches
to.path regex Match target modules whose path matches
to.pathNot regex Exclude target modules whose path matches
circular bool Flag the rule as a circular-dependency check (forbidden only)
orphan bool Flag the rule as an orphan-module check (forbidden only)

Path patterns are regular expressions matched with re.search, so they can match any substring of the res:// path.

Examples

Forbid a dependency between two layers

Prevent UI scripts from importing game logic directly:

{
  "forbidden": [
    {
      "name": "no-ui-to-core",
      "comment": "UI must not depend on core game logic",
      "from": { "path": "ui/" },
      "to": { "path": "core/" }
    }
  ]
}

Restrict allowed dependencies

Only allow player scripts to depend on modules under shared/ or components/:

{
  "allowed": [
    {
      "name": "player-deps",
      "from": { "path": "player/" },
      "to": { "path": "(shared|components)/" }
    }
  ]
}

Require a dependency

Ensure every autoload script preloads config.gd:

{
  "required": [
    {
      "name": "autoloads-need-config",
      "from": { "path": "autoloads/" },
      "to": { "path": "config\\.gd$" }
    }
  ]
}

Forbid circular dependencies in specific modules

{
  "forbidden": [
    {
      "name": "no-cycles-in-core",
      "circular": true,
      "from": { "path": "core/" }
    }
  ]
}

Detect orphan modules

Flag modules that have no dependencies and no dependents:

{
  "forbidden": [
    {
      "name": "no-orphans",
      "severity": "warn",
      "orphan": true,
      "from": { "pathNot": "autoloads/" }
    }
  ]
}

Exclude paths from rule evaluation

{
  "options": {
    "exclude": ["addons/", "test/"]
  },
  "forbidden": [
    {
      "name": "no-core-to-ui",
      "from": { "path": "core/" },
      "to": { "path": "ui/" }
    }
  ]
}

pyproject.toml equivalent

[tool.gdcruiser.options]
exclude = ["addons/"]

[[tool.gdcruiser.forbidden]]
name = "no-ui-to-core"
severity = "error"
comment = "UI must not depend on core game logic"

[tool.gdcruiser.forbidden.from]
path = "ui/"

[tool.gdcruiser.forbidden.to]
path = "core/"

Running with rules

gdcruiser . --config .gdcruiser.json        # explicit config
gdcruiser .                                  # auto-discovers config
gdcruiser . --ignore-rules                   # skip rule evaluation
gdcruiser . --config rules.json -f json      # violations in JSON output
gdcruiser . --validate-config                # validate config and exit

The exit code is non-zero when any error-severity violation is found.

Pre-commit Hook

gdcruiser can run as a pre-commit hook to catch dependency violations on every commit. Add the following to your Godot project's .pre-commit-config.yaml:

repos:
  - repo: https://github.com/Thurbeen/gdcruiser
    rev: v1.0.0  # use the latest release tag
    hooks:
      - id: gdcruiser

By default the hook runs gdcruiser . --no-cycles and only triggers when .gd or .tscn files are changed.

To customize the behavior, pass additional arguments via args:

hooks:
  - id: gdcruiser
    args: ["--config", "rules.json"]
hooks:
  - id: gdcruiser
    args: ["--exclude", "addons"]

If any rule violation is found the hook exits with a non-zero code and blocks the commit.

Development

Install dependencies:

uv sync

Set up pre-commit hooks:

uv run pre-commit install

This installs hooks for pre-commit, commit-msg, and post-checkout stages. On every commit the hooks will:

  • Fix trailing whitespace and line endings
  • Lint and format with Ruff
  • Run the test suite with pytest
  • Enforce Conventional Commits for commit messages

Run the CLI:

uv run gdcruiser

Run tests:

uv run pytest

Run linter:

uv run ruff check .

Format code:

uv run ruff format .

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gdcruiser-1.6.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gdcruiser-1.6.0-py3-none-any.whl (32.1 kB view details)

Uploaded Python 3

File details

Details for the file gdcruiser-1.6.0.tar.gz.

File metadata

  • Download URL: gdcruiser-1.6.0.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gdcruiser-1.6.0.tar.gz
Algorithm Hash digest
SHA256 8fc847434c5fa4b84c9be798ae796704b4795d6473bb3125817e3c16dd74dbdd
MD5 c6d1b43e09f8e31790e6efe245a2787a
BLAKE2b-256 d0bd19f2d23577f1872bff97edfdf546761d483fa82dfe7fdf4cc2675dbe1939

See more details on using hashes here.

Provenance

The following attestation bundles were made for gdcruiser-1.6.0.tar.gz:

Publisher: cd.yaml on Thurbeen/gdcruiser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gdcruiser-1.6.0-py3-none-any.whl.

File metadata

  • Download URL: gdcruiser-1.6.0-py3-none-any.whl
  • Upload date:
  • Size: 32.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gdcruiser-1.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 03d5fe5627935ebfc86b619a40a4823b4343decde60622853825de3daa2be2c2
MD5 25b2d8a403448723061315f6a32996ee
BLAKE2b-256 43ae787b3778abeb7ff81aad3e0bbf082922593db0e3b18b3bf469a9bfb2897a

See more details on using hashes here.

Provenance

The following attestation bundles were made for gdcruiser-1.6.0-py3-none-any.whl:

Publisher: cd.yaml on Thurbeen/gdcruiser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page