From 50b42823caba064be89274610a94aa1d34e8ae5a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 18:44:25 +0200 Subject: [PATCH] feat: add a per-os data_root to profiles Apps such as AetherSX2 named their user data folder only in prose, so the site could neither print it nor show only the OS tabs that apply. data_root keys android, ios, windows, macos and linux, each with the path the user finds and the source_ref that builds it. --- schemas/emulator.schema.json | 16 ++++++++++++++++ tests/test_schema_semantic_gate.py | 26 ++++++++++++++++++++++++++ wiki/profiling.md | 1 + 3 files changed, 43 insertions(+) diff --git a/schemas/emulator.schema.json b/schemas/emulator.schema.json index 23c3356c..3b6062aa 100644 --- a/schemas/emulator.schema.json +++ b/schemas/emulator.schema.json @@ -912,6 +912,22 @@ "type": "string", "description": "Why source_commit is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag: comparing it to HEAD says nothing and recaling would repoint its refs at code the build it documents never had." }, + "data_root": { + "type": "object", + "minProperties": 1, + "propertyNames": {"enum": ["android", "ios", "windows", "macos", "linux"]}, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["path", "source_ref"], + "properties": { + "path": {"type": "string", "minLength": 1, "description": "The folder as the user finds it on that OS, with the environment variable or home prefix the code expands (%APPDATA%, ~, Android/data/)."}, + "source_ref": {"type": "string", "minLength": 1}, + "note": {"type": "string"} + } + }, + "description": "Per operating system, the folder the app reads its user data from, under which standalone_path destinations sit. Only the systems the app runs on are keyed." + }, "upstream_frozen": { "type": "string", "description": "Why upstream_commit is held on purpose rather than followed: the port vendors that upstream tree, synced to that release, or merged that commit. The upstream view is judged against its own revision and a bump leaves it, the way pin_frozen holds source_commit." diff --git a/tests/test_schema_semantic_gate.py b/tests/test_schema_semantic_gate.py index 6c06b3d0..746d2822 100644 --- a/tests/test_schema_semantic_gate.py +++ b/tests/test_schema_semantic_gate.py @@ -96,3 +96,29 @@ class SemanticManifestValidation(unittest.TestCase): if __name__ == "__main__": unittest.main() + + +try: + import jsonschema +except ImportError: + jsonschema = None + + +@unittest.skipIf(jsonschema is None, "jsonschema unavailable") +class DataRootIsKeyedBySystem(unittest.TestCase): + """AetherSX2 gave Android/data/xyz.aethersx2.android only in its notes: + the site could not print the folder, nor which OS tabs apply.""" + + def _errors(self, value): + schema = json.loads((REPO_ROOT / "schemas/emulator.schema.json").read_text(encoding="utf-8")) + validator = jsonschema.Draft202012Validator(schema["properties"]["data_root"]) + return list(validator.iter_errors(value)) + + def test_a_system_with_its_path_and_reference(self): + self.assertEqual(self._errors({"android": { + "path": "Android/data/xyz.aethersx2.android/files", "source_ref": "a.kt:1"}}), []) + + def test_an_unknown_system_or_a_path_without_reference(self): + self.assertTrue(self._errors({"amiga": {"path": "x", "source_ref": "y"}})) + self.assertTrue(self._errors({"windows": {"path": "%APPDATA%/X"}})) + self.assertTrue(self._errors({})) diff --git a/wiki/profiling.md b/wiki/profiling.md index 420c6336..0e106927 100644 --- a/wiki/profiling.md +++ b/wiki/profiling.md @@ -591,6 +591,7 @@ does not look. | `known_hash_adler32` | expected Adler-32 hash (Dolphin's DSP ROMs). Pair with `adler32_byteswap` when the code hashes 16-bit byte-swapped data | | `adler32_pairs_with`, `adler32_pairs` | the file hashed together with this one and the combinations the code accepts, this file's value first, in table order: Dolphin warns on a DSP ROM pair outside its `known_roms` even when each file passes alone | | `pin_frozen` | why `source_commit` is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag | +| `data_root` | per operating system (`android`, `ios`, `windows`, `macos`, `linux`), the folder the app reads its user data from, under which `standalone_path` destinations sit: `path` as the user finds it (`Android/data//files`, `%APPDATA%/...`), with the `source_ref` that builds it. Only the systems the app runs on are keyed | | `upstream_frozen` | why `upstream_commit` is held: the port vendors that upstream tree, synced to that release, or merged that commit. The upstream view is read at its own pin and a bump leaves it. Without it, a port that is a GitHub fork of its upstream follows the merge base of the two | | `prose_written_at` | the pin a reader confirmed the prose citations against. When the profile's history shows a text living under several pins, profile_sync takes this one as the revision the text describes, so a bump is no longer refused for a citation already reread. It never overrides a history with a single candidate | | `version_source` | for a project published outside any forge, `{url, pattern}`: the page that announces releases and a regular expression whose first group is the latest version. `profile_sync --check-version` compares it with `core_version`, and `check_freshness --profiles` calls the profile stale when they differ. `no_genuine_copy` names an announced version found only rebuilt or re-signed: the profile stays on the newest unmodified build and is not stale until the page announces a later one |