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 writes the directive the version calls for, and inserts after the last import 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
--checkdry 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
targetsorexcludecannot 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;-tdisables 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.
MOJ_IMPORT support
Versions before 26.3 were using the #moj_import directive instead of #include. Setting in ggraft.toml the target version strictly below 26.3 will automatically convert behavior of declare operator to output the #moj_import directive.
Planned Features
- python module API
Metadata
Release files for ggraft 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ggraft-1.1.1.tar.gz | 40.8 kB | With provenance | Details for ggraft-1.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ggraft-1.1.1-py3-none-any.whl | Python 3 | none | any | Details for ggraft-1.1.1-py3-none-any.whl |
Total release size: 68.3 kB
Release files / ggraft-1.1.1.tar.gz
| Download URL | ggraft-1.1.1.tar.gz |
|---|---|
| Size | 40.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1369f850dd43643b8498e68dacc860169cdae5ac80cea286a589073b528fb46a
|
|
BLAKE2b-256 checksum How to use checksums |
23f9269b8e06e65073b156e6af4258ca2e0b07854dfd8dc6b6e4b12cecc55a0e
|
| 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 logRelease files / ggraft-1.1.1-py3-none-any.whl
| Download URL | ggraft-1.1.1-py3-none-any.whl |
|---|---|
| Size | 27.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fe4f528ed4868272a0d0a38958b60bf80997f18bf1659f0613fdbccf52a87d0f
|
|
BLAKE2b-256 checksum How to use checksums |
be26521d17226ae75626b0d982defda221c04252d41397fb8fcbc2a78a44fde8
|
| 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 logSecurity for ggraft 1.1.1
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.
Attestation for ggraft-1.1.1.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 logA 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.