Key the shaders step by the translator the build actually uses.

The shaders step key hashed only the kit's bin/nx2navi_xlate.id, but the build takes the translator from the kit's bin/ or, in a source checkout, from build/xlate/ (xlate_source). From a source checkout, main() worked out the shaders key right after the compile step and stopped with a FileNotFoundError on bin/nx2navi_xlate.id before any shader was prepared.

The key now hashes the identity file beside the translator xlate_source finds, so a source checkout is keyed by build/xlate/nx2navi_xlate.id and a kit's key is unchanged. A build without a translator stops with the existing message that names tools/build_xlate.py. The prompts test kit gets a translator stand-in beside its identity file, and a new test covers a kit that has the translator only under build/xlate/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael Schienbein
2026-11-09 12:23:30 -06:00
committed by chrg6
parent 6910723aed
commit b6a5cc302c
3 changed files with 40 additions and 4 deletions

View File

@@ -305,7 +305,8 @@ pruning, unsafe archives, the background update (a release staged beside the run
`staged.json` marker, a staged release installed after a restart, no install while the game runs, the install once
it exited, nothing by itself with Install Updates Automatically off, a failed staging tried again after the next
check), the font decoding on a system font wrapped like the game's, `make_game.py` on a synthetic
`.nup` (events, skipped steps, `--rebuild` without the dump, step keys), the RomFS step beside the lift and compile
`.nup` (events, skipped steps, `--rebuild` without the dump, step keys, the shaders key following the translator the
build uses), the RomFS step beside the lift and compile
and its failure stopping the build, the prompts step with a stand-in prompt tool (when it runs again, and that it
makes no other step run), the button art a release copies and the packages and tools the prompts need in a release,
the compile step's object cache with the system's C compiler and its

View File

@@ -14,7 +14,8 @@ Run: python tests/launcher_test.py (Python 3.14 for the zstd tests; the window
- gamefiles: the .bfotf font wrapping and zstd dictionaries, on a font of this system made into a fake RomFS
- the button art of a release: the files tools/prompt_art.py copies, and the packages and tools the prompts need
- make_game.py: a synthetic .nup through the check, exefs and romfs steps with --events, skipped steps on a second
run, --rebuild without the game files, the prompts step with a stand-in prompt tool, the object cache of the compile
run, --rebuild without the game files, the shaders key following the translator the build uses (the kit's bin/ or
a source checkout's build/xlate/), the prompts step with a stand-in prompt tool, the object cache of the compile
step with the system's C compiler, and its windres call under folders with spaces (a script in windres's place)
- jobs: an install driven by a fake make_game.py, with and without the prompts row
- the window: --selftest draws every screen (skipped without a display)
@@ -1412,6 +1413,37 @@ class MakeGameTest(Tmp):
steps = json.loads((self.out / 'build-cache' / 'state.json').read_text())['steps']
self.assertNotIn('romfs', steps)
def test_shaders_key_names_the_translator_the_build_uses(self):
"""A source checkout has its translator in build/xlate/ (tools/build_xlate.py), not in the kit's bin/: the
shaders key follows its identity, so a rebuilt translator prepares the shaders again. A kit's bin/ comes first
when both exist, and a kit with no translator is refused with the way to build one."""
from unittest import mock
kit = self.tmp / 'kit'
(kit / 'tools').mkdir(parents=True)
(kit / 'tools' / 'prepare_shaders.py').write_text('')
xlate = kit / 'build' / 'xlate'
xlate.mkdir(parents=True)
(xlate / self.mg.XLATE_NAME).write_bytes(b'')
(xlate / 'nx2navi_xlate.id').write_text('xlate 1\n')
def key():
return self.mg.step_key('shaders', 'game', self.out)
with mock.patch.object(self.mg, 'KIT', kit):
first = key()
(xlate / 'nx2navi_xlate.id').write_text('xlate 2\n')
self.assertNotEqual(key(), first)
(kit / 'bin').mkdir()
(kit / 'bin' / self.mg.XLATE_NAME).write_bytes(b'')
(kit / 'bin' / 'nx2navi_xlate.id').write_text('xlate 3\n')
from_bin = key()
(xlate / 'nx2navi_xlate.id').write_text('xlate 4\n')
self.assertEqual(key(), from_bin)
shutil.rmtree(kit / 'bin')
shutil.rmtree(xlate)
with contextlib.redirect_stderr(io.StringIO()) as err, self.assertRaises(SystemExit):
key()
self.assertIn('tools/build_xlate.py', err.getvalue())
def test_prompts_step(self):
"""With button art in the kit, the last step draws the button prompts (a fake tools/pad_prompts.py), one
progress event per folder. It runs again only when the art, the bind_ lines or a folder changed, and then no
@@ -1420,6 +1452,7 @@ class MakeGameTest(Tmp):
kit = self.tmp / 'kit'
(kit / 'tools').mkdir(parents=True)
(kit / 'bin').mkdir()
(kit / 'bin' / self.mg.XLATE_NAME).write_bytes(b'') # the translator the shaders key names (xlate_source)
(kit / 'bin' / 'nx2navi_xlate.id').write_text('xlate 1\n')
(kit / 'tools' / 'prepare_shaders.py').write_text('')
for name in ('prompt_art.py', 'button_icons.py'):

View File

@@ -1543,8 +1543,10 @@ def step_key(step, identity, out=None):
f'|cpu {cpu_tag()}')
if step == 'compile':
return compile_key
# shaders: the built game and the translator that prepared them
return f'shaders-1|{compile_key}|{sha256_file(KIT / "tools" / "prepare_shaders.py")}|{sha256_file(KIT / "bin" / "nx2navi_xlate.id")}'
# shaders: the built game and the translator that prepared them, the one xlate_source finds (the kit's bin/, or a
# source checkout's build/xlate/)
return (f'shaders-1|{compile_key}|{sha256_file(KIT / "tools" / "prepare_shaders.py")}'
f'|{sha256_file(xlate_source() / "nx2navi_xlate.id")}')
def main():