Skip to main content

ggraft

GLSL Graft is a cli tool allowing declarative injection patches for vanilla Minecraft shaders.

Overriding a core shader means copying Mojang's file and changing one line. Do that across twenty shaders and every Minecraft update becomes twenty manual re-copies, with no record of what you actually changed. ggraft keeps the edit under version control instead of the result: you declare which line to change, and it re-derives the overrides from any version on demand.

Terminology

Concept Name
The vanilla GLSL going in base
A file declaring edits patch
Which shader it's for target
Where in the source injection point (or anchor)
One edit inside a patch injection
What it does operation: insert, replace, wrap, declare, function
Anchor didn't match unresolved injection
Anchor matched N times ambiguous injection
GLSL coming out patched shader

Install

From PyPI

pip install ggraft

From source

Needs Python 3.11+ and Click.

pip install -e .

Quick start

ggraft init 26.3      # write ggraft.toml
ggraft pull           # fetch bases from misode/mcmeta
ggraft build          # apply patches -> patched shaders

Project configuration

ggraft.toml, found by walking up from the working directory. Paths resolve relative to it.

[base]
version = "26.3"                      # Minecraft version to pull
dir     = ".ggraft/base"              # where bases land (gitignore this)
source  = "assets/minecraft/shaders"  # subtree to pull, stripped from local paths
repo    = "misode/mcmeta"             # optional

[patches]
dir = "patches"

[output]
dir    = "assets/minecraft/shaders"   # required
header = true                         # stamp a provenance comment

Targets are relative to base.source: core/terrain.vsh reads from .ggraft/base/core/terrain.vsh and writes to <output.dir>/core/terrain.vsh.

Patches

name    = "iso-world"
targets = ["core/terrain.vsh", "core/rendertype_*.vsh"]
exclude = ["core/rendertype_lines.vsh"]

[[injection]]
op      = "declare"
include = "foo:iso.glsl"

[[injection]]
op    = "replace"
match = "gl_Position = ProjMat * ModelViewMat * vec4({expr}, 1.0);"
with  = "gl_Position = iso_position(ProjMat, ModelViewMat, vec4({expr}, 1.0));"

Injections apply in order, to every matching target. Patches stack in filename order.

Anchors

Key Meaning
match the anchor pattern
regex = true treat match as a regex, with named groups
multiline = true let one {name} (or a regex .) span lines (false by default)
occurrence = N take the Nth match (1-based)
every = true apply to all matches

occurrence and every cannot both be set. multiline is only about letting a single {name} (or a regex .) cross one. A capture is non-greedy and must match at least one character, so vec4({expr}) does not match vec4().

Under regex = true the pattern is used as written, with ^ and $ matching at every line boundary, and its named groups (?P<name>...) fill the same {name} placeholders. In the replacement, a {name} that captured nothing is left exactly as it is, so GLSL braces and unrelated placeholders survive untouched.

No match is an unresolved injection, and so is an occurrence past the number of matches. More than one match without occurrence or every is an ambiguous injection, reported with the line of every match. Both name the patch, the injection index and the target, and both fail the build.

Operations

Operation Keys Effect
replace match, with swap the anchored text
insert match, text, where (before/after, after by default) add text beside it
wrap match, prefix and/or suffix surround it, keeping it
declare include or text add a top-level declaration; no anchor
function function, text, at (HEAD/TAIL/RETURN/INVOKE/BEFORE/AFTER, HEAD by default), occurrence, and call plus where for INVOKE add code in or beside a function; no anchor

replace, insert and wrap are the anchored operations, so they also take regex, multiline, occurrence and every from Anchors. declare and function find their own place in the file and take no anchor.

declare inserts after the last #include at conditional-nesting depth zero, falling back to #extension then #version, and is idempotent. Appending after the last #include in the file would strand it inside an #ifdef branch that some shader variants skip.

Function injections

function targets a function body rather than a piece of text, the way a regular mixin does. at names a structural point, not an anchor pattern.

[[injection]]
op       = "function"
function = "main"
at       = "HEAD"           # HEAD by default
text     = "iso_setup();"
Point Where the text lands
HEAD first statement of the body
TAIL last statement, before the closing brace
RETURN before every return, nested ones included
INVOKE around every statement that calls call
BEFORE above the definition, at top level
AFTER below the definition, at top level

Text lands on a line of its own, indented like its neighbours. Multi-line text is indented line by line and keeps its blank lines; a one-line body stays on one line. Nothing is substituted, so {x} stays {x}.

INVOKE needs call, the function being called, and takes where (before by default, or after):

[[injection]]
op       = "function"
function = "main"
at       = "INVOKE"
call     = "fog_distance"
where    = "after"
text     = "iso_note_fog();"

It wraps the whole statement holding the call, never the expression inside it, and hits every call in the body. for one call in particular, use insert with a match anchor. A call that opens a block, like if (helper(x)) {, has no statement to follow: where = "after" is unresolved there, before always works.

Prototypes, calls and commented-out code are not definitions. An overloaded name is an ambiguous injection until occurrence = N picks one, and RETURN on a function that never returns is an unresolved injection, use TAIL insteaad.

Commands

Command Purpose
ggraft pull [version] fetch bases; --force refetches, -j sets parallelism. Pulling another version replaces the base
ggraft build apply patches; --check dry run, -t filters by glob
ggraft targets show which patches hit which targets
ggraft init [version] write a starter config; --force overwrites

-h/--help works on the group and every command, -V/--version prints the version, and -c/--config points at a specific ggraft.toml instead of searching upwards. pull --token also reads GITHUB_TOKEN.

Build guarantees

  • --check dry run without output writes.
  • A target pattern matching no base file fails the build. What a patch orphaned by a Minecraft update looks like.
  • A patch applying to nothing fails the build, so a mistake in targets or exclude cannot disable a patch silently.
  • Stale output is pruned. Shaders ggraft wrote are recorded in .ggraft/manifest.json, and a target dropped from a patch has its override deleted on the next build. Only manifest entries are ever deleted; -t disables pruning, since a partial build cannot tell what is stale.

Bases

Uses misode/mcmeta. Tree walk might be rate limited. Set GITHUB_TOKEN or pass --token to bypass the limit.

Tests

python -m unittest discover -s tests   # everything
python -m unittest tests.test_cli      # one module

One module per layer: test_structure, test_glsl, test_anchor, test_operations, test_engine, test_sources, test_cli. Shared shaders, project files and a network-free mcmeta client live in tests/fixtures.py.

Planned Features

  • python module API
  • backport handling of #moj_import rather than #include

Metadata

Release files for ggraft 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ggraft 1.1.0
File Size Uploaded Provenance Actions
ggraft-1.1.0.tar.gz 39.3 kB With provenance Details for ggraft-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ggraft 1.1.0
File Interpreter ABI Platform Actions
ggraft-1.1.0-py3-none-any.whl Python 3 none any Details for ggraft-1.1.0-py3-none-any.whl

Total release size: 65.9 kB

Release files / ggraft-1.1.0.tar.gz

Download URL ggraft-1.1.0.tar.gz
Size 39.3 kB
Tags Source
SHA-256 checksum
How to use checksums
2817d07643e580957486a00fed3816bb9b850ef43212377a9485501768ed49e4
BLAKE2b-256 checksum
How to use checksums
50a885369be7b1903a32021e58d65d37d34159fab12abe795dcbdc3ab3c43d88
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / ggraft-1.1.0-py3-none-any.whl

Download URL ggraft-1.1.0-py3-none-any.whl
Size 26.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
923b0a02f43e595c4128a4a8870531024a3b6c0268514f0bef4c2557051161b9
BLAKE2b-256 checksum
How to use checksums
d899fbe4db9607aaaac04bcf4f5383dc6f0e660e97e8e80b4eef05308710e967
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Security for ggraft 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ggraft-1.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page