Skip to content

Commit 73bf2ce

Browse files
authored
Merge pull request #12 from Tratosca/fix/module-index-crlf
fix(runtime): write the module index with unix line endings
2 parents 27c3236 + d937d6f commit 73bf2ce

3 files changed

Lines changed: 48 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
<!-- SPDX-License-Identifier: MPL-2.0 -->
22
# Changelog
33

4+
## 0.5.2
5+
6+
A runtime built on Windows loads its modules again.
7+
8+
### Fixed
9+
10+
- An `autoexec.bin` built on Windows no longer stops with
11+
`STOP: one or more runtime modules violate their contract`, preceded by one
12+
`FAILED: unsafe runtime module directory` per selected module. The module
13+
index the runtime reads to know what to load is written line by line from
14+
Python, which translates each newline to the platform's own unless it is told
15+
not to. On Windows that appended a carriage return to every line, and the
16+
shell that reads the index takes it as the last character of the directory
17+
name, so no module passed the name check. Affects 0.5.0 and 0.5.1; builds
18+
made on Linux and macOS were never affected, and 0.4.0 predates the index.
19+
420
## 0.5.1
521

622
Reinserting the drive works. It used to be refused, and before that it was what

‎tests/test_mod_generator.py‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,31 @@ def test_build_adds_required_core_without_adding_sibling_features(self):
111111
)
112112
self.assertEqual(result.patches, ("core", "keyshift"))
113113
plain = load_firmware_codec().read_autoexec(result.output, key)
114-
normalized = plain.replace(b"\r\n", b"\n")
115114

116-
self.assertIn(b"compatibility\ncore\nkeyshift\n", normalized)
117-
self.assertNotIn(b"\nstems\n", normalized)
115+
self.assertIn(b"compatibility\ncore\nkeyshift\n", plain)
116+
self.assertNotIn(b"\nstems\n", plain)
117+
118+
def test_the_module_index_is_written_with_unix_line_endings(self):
119+
"""The index is read line by line by /bin/sh on the player, where a
120+
trailing CR is part of the directory name and fails the module-name
121+
check. Python translates \\n to os.linesep unless told not to, so a
122+
build made on Windows shipped an index no module could be loaded from.
123+
The source assertion carries the test on Linux and macOS, where that
124+
translation never happens and the built image cannot show the fault."""
125+
builder = (REPOSITORY / "tools/rx3_runtime/build.py").read_text()
126+
self.assertRegex(
127+
builder, r'modules / "index"\)\.write_text\((?s:.*?)newline=""'
128+
)
129+
130+
with tempfile.TemporaryDirectory() as directory:
131+
directory = Path(directory)
132+
key = directory / "aes256.key"
133+
key.write_bytes(b"0123456789012345678901234567890\n")
134+
result = build_runtime(
135+
"1.19", ["keyshift"], key, directory, root=REPOSITORY
136+
)
137+
plain = load_firmware_codec().read_autoexec(result.output, key)
138+
self.assertNotIn(b"compatibility\r", plain)
118139

119140
def test_the_session_log_ships_only_when_its_module_is_selected(self):
120141
"""say() runs before any module is loaded, so the orchestrator reads
@@ -133,14 +154,14 @@ def test_the_session_log_ships_only_when_its_module_is_selected(self):
133154
"1.19", ["keyshift"], key, directory, root=REPOSITORY
134155
)
135156
self.assertNotIn("logging", quiet.patches)
136-
index = codec.read_autoexec(quiet.output, key).replace(b"\r\n", b"\n")
157+
index = codec.read_autoexec(quiet.output, key)
137158
self.assertNotIn(b"\nlogging\n", index)
138159

139160
verbose = build_runtime(
140161
"1.19", ["keyshift", "logging"], key, directory, root=REPOSITORY
141162
)
142163
self.assertIn("logging", verbose.patches)
143-
index = codec.read_autoexec(verbose.output, key).replace(b"\r\n", b"\n")
164+
index = codec.read_autoexec(verbose.output, key)
144165
self.assertIn(b"\nlogging\n", index)
145166

146167
def test_the_logging_module_warns_about_pulling_the_drive_out(self):

‎tools/rx3_runtime/build.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,12 @@ def build_runtime(
485485
notify("Compiling the performance core…")
486486
compile_arm_hook(patch.directory / patch.arm_hook.source, target)
487487
module_index = ["compatibility"] + [patch.runtime_directory for patch in selected]
488-
(modules / "index").write_text("".join(f"{item}\n" for item in module_index), encoding="ascii")
488+
# newline="" keeps Python from translating these \n to os.linesep. The
489+
# index is read by /bin/sh on the player, where a trailing CR is part of
490+
# the directory name and fails the module-name check on every line.
491+
(modules / "index").write_text(
492+
"".join(f"{item}\n" for item in module_index), encoding="ascii", newline="",
493+
)
489494
(staging / "autoexec.sh").chmod(0o755)
490495

491496
notify("Creating and encrypting autoexec.bin…")

0 commit comments

Comments
 (0)