Add an update script that re-applies changed patches

After a pull that changed a patch, prepare.py stopped: the patch neither
applied nor reverse-applied to a tree holding its earlier version. Now
it looks for the earlier version applied in that component -- the copy
it records in build/applied-patches, or a version from this
repository's history -- reverts exactly it and applies the new one.
It never resets a repository; when nothing matches it stops and names
the repository with other edits, and it restores the earlier version
if the new one does not apply.

scripts/update.sh runs build.sh, the base install and
install-runtime.sh, and refuses to start while a Riot Client or League
process is open, since the install steps stop Darling.

Checked on a sandbox Indium checkout holding the previous patch: the
result matches a clean apply, a second run is a no-op, an unrelated
local edit survives and a conflicting one stops with the error.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
lucannez64andClaude Opus 5.5 committed 2026-10-15 22:11:26 +02:00
1 parent 3e76087dbe
commit 96da34a633
4 files changed
+70 -3

No files matched your search

+11 -1
View File
@@ -67,7 +67,17 @@ FPS counters, game presentation requests and physical display refresh are differ
On the Ryzen 5 5500 / RX 6600 test PC, ARAM on Hexgate went from 84 to 135 FPS average over 2026-10-13/14, with the slowest 10-second stretches around 119 in the best match (one match so far with Hyprland's direct scanout off; [status](docs/STATUS.md#game-fps-2026-10-1314)). The game-process speed switches and their opt-out files are listed in [setup](docs/SETUP.md#game-process-switches), and passive match capture is described in [testing](docs/TESTING.md#passive-match-capture).
For existing installations, [incremental update instructions](docs/SETUP.md#updating-an-existing-runtime) explain the server and memory installers, user-owned backups and the full Darling restart needed to activate replacements. Perform activation only after manually exiting the game and both clients.
To update an existing installation, exit the game and both clients, then:
```sh
git pull
./scripts/update.sh
sh scripts/run.sh
```
`update.sh` re-applies the patches that changed (reverting the earlier version of each, so other edits in the source tree survive), rebuilds incrementally, and reinstalls the base runtime and the per-user overrides. It refuses to run while a Riot or League process is open.
For updating single components, [incremental update instructions](docs/SETUP.md#updating-an-existing-runtime) explain the server and memory installers, user-owned backups and the full Darling restart needed to activate replacements. Perform activation only after manually exiting the game and both clients.
## Layout
+2
View File
@@ -28,6 +28,8 @@ The normal launcher starts neither an Electron inspector nor client API/UI helpe
## Updating an existing runtime
`./scripts/update.sh` updates everything after `git pull`: `build.sh`, then the base install, then `install-runtime.sh`. When a pull changes a patch, `prepare.py` finds the earlier version applied in that component (its copy in `build/applied-patches`, or a version from this repository's history), reverts it, and applies the new one. It never resets a repository; if neither version matches, it stops and names the repository with other edits. The steps below update single components instead.
These commands assume the pinned, patched source tree and its configured Makefiles already exist. From the repository root, build the two components with one worker to reduce resource contention:
```sh
+28 -2
View File
@@ -8,6 +8,21 @@ p.add_argument('--source',type=Path,default=ROOT/'build/darling-src')
p.add_argument('--check-only',action='store_true',help='validate against temporary base indexes, without changing existing source')
a=p.parse_args();source=a.source.resolve();manifest=json.loads((ROOT/'upstream/manifest.json').read_text())
def run(args,**kwargs):return subprocess.run(args,check=True,**kwargs)
def applies(repo,patch,reverse=False):
return subprocess.run(['git','-C',str(repo),'apply',*(['--reverse'] if reverse else []),'--check','-'],input=patch,text=True,
stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0
# The rendered text of each patch as last applied, so a later version can revert exactly it.
applied=ROOT/'build/applied-patches'
def record_patch(component,patch):
applied.mkdir(parents=True,exist_ok=True);(applied/Path(component['patch']).name).write_text(patch)
def previous_patches(component):
"""Earlier applied versions of a component's patch: the recorded copy, then each version in this repository's history."""
recorded=applied/Path(component['patch']).name
if recorded.exists():yield recorded.read_text()
history=subprocess.run(['git','-C',str(ROOT),'log','--format=%H','--',component['patch']],text=True,capture_output=True).stdout.split()
for commit in history:
shown=subprocess.run(['git','-C',str(ROOT),'show',f"{commit}:{component['patch']}"],text=True,capture_output=True)
if shown.returncode==0:yield render(shown.stdout)
if not (source/'.git').exists():
if a.check_only:p.error('check-only requires an existing pinned source checkout')
source.parent.mkdir(parents=True,exist_ok=True)
@@ -35,9 +50,20 @@ for component in manifest['components']:
run(['git','-C',str(repo),'read-tree',component['revision']],env=env)
run(['git','-C',str(repo),'apply','--cached','--check','--whitespace=nowarn','-'],input=patch,text=True,env=env)
else:
already=subprocess.run(['git','-C',str(repo),'apply','--reverse','--check','-'],input=patch,text=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0
already=applies(repo,patch,reverse=True)
if not already and not applies(repo,patch):
# An older version of this patch is applied (the checkout predates a pull). Revert exactly that
# version, never a reset, so other edits in the repository survive.
old=next((text for text in previous_patches(component) if applies(repo,text,reverse=True)),None)
if old is None:raise SystemExit(f"{component['path']}: {component['patch']} neither applies nor matches an earlier applied version; "
f"the checkout has other edits. Commit or discard them in {repo}, then run this again.")
print(f"Replacing the earlier {component['patch']} in {component['path']}",flush=True)
run(['git','-C',str(repo),'apply','--reverse','--whitespace=nowarn','-'],input=old,text=True)
if not applies(repo,patch):
run(['git','-C',str(repo),'apply','--whitespace=nowarn','-'],input=old,text=True)
raise SystemExit(f"{component['path']}: the new {component['patch']} does not apply to the pinned revision; the earlier version was restored")
if not already:
run(['git','-C',str(repo),'apply','--check','-'],input=patch,text=True)
run(['git','-C',str(repo),'apply','--whitespace=nowarn','-'],input=patch,text=True)
record_patch(component,patch)
print(('Checked ' if a.check_only else 'Prepared ')+component['path'],flush=True)
print('All',len(manifest['components']),'component patches validated' if a.check_only else 'component patches ready')
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# Bring an existing build and installation up to date after `git pull`: re-apply the patches that changed
# (prepare.py reverts the earlier version it finds applied), rebuild incrementally, and reinstall the base
# runtime and the per-user overrides. Run it with no Riot/League session open; it never stops one.
set -eu
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$repo"
if [ ! -d build/darling-src/build ]; then
echo 'No existing build; follow the first-time setup in README.md instead.' >&2
exit 1
fi
# The install steps shut Darling down and stop leftover Riot processes, so refuse rather than end a session.
python3 - <<'PY'
from pathlib import Path
for entry in Path('/proc').iterdir():
if not entry.name.isdigit():
continue
try:
command_line = (entry / 'cmdline').read_bytes()
except OSError:
continue
if b'/Riot Client.app/' in command_line or b'/League of Legends.app/' in command_line:
raise SystemExit('A Riot Client or League process is running; exit the game and both clients, then run this again.')
PY
./scripts/build.sh
python3 build/runtime/tools/stop-darling-runtime.py
sudo cmake --install build/darling-src/build
./scripts/install-runtime.sh
echo 'Update installed. Start Darling with: sh scripts/run.sh'