fix: write each export as its bytes and mode

write_text translated line ends on Windows, so FirmwareDatabase.cs's
CRLF would have come out CR CR LF; it replaced the published file in
place, so a run stopped mid-write left it truncated; and it dropped the
mode, while rocknix-systems is 100755 in ROCKNIX's tree. Files are
written atomically as UTF-8 bytes, and an exporter states the mode of
any file the platform does not publish as 0644.
This commit is contained in:
Abdessamad Derraz committed 2026-10-20 18:27:22 +02:00
1 parent cbe769e74c
commit 85c2433c98
4 files changed
+55 -5

No files matched your search

+16 -4
View File
@@ -562,10 +562,7 @@ def export_platform(
platform_dir = output_dir / platform
_remove_stale(platform_dir, set(produced))
for relative, content in produced.items():
path = platform_dir / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
_write_produced(platform_dir, produced, exporter.file_modes())
changes = landed_changes(exporter, systems)
applied, filled = changes.applied, changes.filled
@@ -663,6 +660,21 @@ def export_platform(
return True, messages
def _write_produced(root: Path, produced: dict[str, str], modes: dict[str, int]) -> None:
"""Write each file as its bytes, whole or not at all, with its mode.
write_text translated line ends on Windows, so FirmwareDatabase.cs's
CRLF would have come out CR CR LF, and a run stopped mid-write left a
truncated file in exports/. The scratch file mkstemp makes is 0600: the
mode is set from what the platform's own file has.
"""
for relative, content in produced.items():
path = root / relative
path.parent.mkdir(parents=True, exist_ok=True)
write_bytes_atomic(str(path), content.encode("utf-8"))
path.chmod(modes.get(relative, 0o644))
def write_report(path: Path, report: dict) -> None:
"""What one platform's export changed, for the page that publishes it.
+5
View File
@@ -126,6 +126,11 @@ class BaseExporter(ABC):
"""
return True
@staticmethod
def file_modes() -> dict[str, int]:
"""The mode of each file the platform publishes other than 0644."""
return {}
@staticmethod
def keys_by_system() -> bool:
"""Whether the platform finds a file through its system's own name.
+5
View File
@@ -77,6 +77,11 @@ class Exporter(BatoceraExporter):
def native_filename() -> str:
return "rocknix-systems"
@staticmethod
def file_modes() -> dict[str, int]:
# 100755 in ROCKNIX's tree at the pinned tag (git/trees 20261001).
return {"rocknix-systems": 0o755}
@staticmethod
def native_sources() -> dict[str, str]:
return {"rocknix-systems": SOURCE_URL, EMULATORS_MK: EMULATORS_URL}
+29 -1
View File
@@ -1153,7 +1153,35 @@ class AnExportLeavesNoStaleFile(unittest.TestCase):
def test_the_export_clears_before_it_writes(self):
source = (REPO_ROOT / "scripts" / "export_native.py").read_text()
body = source[source.index("def export_platform("):]
self.assertLess(body.index("_remove_stale("), body.index(".write_text("))
self.assertLess(body.index("_remove_stale("), body.index("_write_produced("))
class AnExportIsWrittenAsItsBytes(unittest.TestCase):
"""write_text translated line ends on Windows (FirmwareDatabase.cs is
CRLF, it would have become CR CR LF), replaced the published file in
place, and dropped the mode: rocknix-systems is 100755 upstream."""
def test_bytes_mode_and_atomicity(self):
import stat # noqa: PLC0415
import export_native # noqa: PLC0415
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
root = Path(tmp)
export_native._write_produced(root, {
"FirmwareDatabase.cs": "a\r\nb\r\n",
"rocknix-systems": "#!/usr/bin/python\n",
}, {"rocknix-systems": 0o755})
self.assertEqual((root / "FirmwareDatabase.cs").read_bytes(), b"a\r\nb\r\n")
mode = lambda name: stat.S_IMODE((root / name).stat().st_mode) # noqa: E731
self.assertEqual(mode("rocknix-systems"), 0o755)
self.assertEqual(mode("FirmwareDatabase.cs"), 0o644)
self.assertEqual(sorted(p.name for p in root.iterdir()),
["FirmwareDatabase.cs", "rocknix-systems"])
def test_rocknix_says_its_script_is_executable(self):
self.assertEqual(discover_exporters()["rocknix"].file_modes(),
{"rocknix-systems": 0o755})
class AnExportWithoutTruthIsRefused(unittest.TestCase):