Skip to content

Commit 0e2908e

Browse files
committed
[green] Upsert deterministic Android AVD settings
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 4c0fa20 commit 0e2908e

2 files changed

Lines changed: 87 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -309,18 +309,19 @@ jobs:
309309
# 1280x800 logical tablet surface, matching the Android test geometry.
310310
# Keep userdata at 2 GiB so the complete API 34 image fits the hosted
311311
# runner after its pinned SDK, build-tools, and NDK are installed.
312-
printf '%s\n' \
313-
'hw.ramSize=3072' \
314-
'hw.cpu.ncore=2' \
315-
'vm.heapSize=512' \
316-
'disk.dataPartition.size=2048M' \
317-
'hw.lcd.width=2560' \
318-
'hw.lcd.height=1600' \
319-
'hw.lcd.density=320' \
320-
'hw.initialOrientation=landscape' \
321-
'hw.gpu.enabled=yes' \
322-
'hw.gpu.mode=swiftshader_indirect' \
323-
>> "$AVD_CONFIG"
312+
# avdmanager pre-populates these keys. Upsert them so the emulator
313+
# cannot select an earlier device-profile value from a duplicate.
314+
python3 ../tools/ci/android_avd_config.py "$AVD_CONFIG" \
315+
--set 'hw.ramSize=3072' \
316+
--set 'hw.cpu.ncore=2' \
317+
--set 'vm.heapSize=512' \
318+
--set 'disk.dataPartition.size=2048M' \
319+
--set 'hw.lcd.width=2560' \
320+
--set 'hw.lcd.height=1600' \
321+
--set 'hw.lcd.density=320' \
322+
--set 'hw.initialOrientation=landscape' \
323+
--set 'hw.gpu.enabled=yes' \
324+
--set 'hw.gpu.mode=swiftshader_indirect'
324325
- name: Boot headless emulator (KVM + software GPU)
325326
shell: bash
326327
run: |

tools/ci/android_avd_config.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: MIT
3+
# Part of PyBLE (https://pyble.dev) — see /LICENSE.
4+
"""Apply deterministic, duplicate-free Android AVD configuration overrides."""
5+
6+
from __future__ import annotations
7+
8+
import argparse
9+
import re
10+
from collections.abc import Sequence
11+
from pathlib import Path
12+
13+
VALID_KEY = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*")
14+
15+
16+
def _parse_overrides(assignments: Sequence[str]) -> list[tuple[str, str]]:
17+
parsed: list[tuple[str, str]] = []
18+
seen: set[str] = set()
19+
for assignment in assignments:
20+
key, separator, value = assignment.partition("=")
21+
if (
22+
not separator
23+
or not VALID_KEY.fullmatch(key)
24+
or not value
25+
or "\n" in value
26+
or "\r" in value
27+
):
28+
raise ValueError(
29+
f"override must be a single-line KEY=VALUE assignment: {assignment!r}"
30+
)
31+
if key in seen:
32+
raise ValueError(f"duplicate requested override: {key}")
33+
seen.add(key)
34+
parsed.append((key, value))
35+
return parsed
36+
37+
38+
def apply_overrides(config: Path, assignments: Sequence[str]) -> None:
39+
"""Replace all existing copies of each requested key with one canonical value."""
40+
overrides = _parse_overrides(assignments)
41+
controlled_keys = {key for key, _ in overrides}
42+
retained: list[str] = []
43+
for line in config.read_text(encoding="utf-8").splitlines():
44+
candidate, separator, _ = line.partition("=")
45+
if separator and candidate.strip() in controlled_keys:
46+
continue
47+
retained.append(line)
48+
49+
retained.extend(f"{key}={value}" for key, value in overrides)
50+
config.write_text("\n".join(retained) + "\n", encoding="utf-8")
51+
52+
53+
def main() -> int:
54+
parser = argparse.ArgumentParser(
55+
description="Apply duplicate-free KEY=VALUE overrides to an AVD config.ini."
56+
)
57+
parser.add_argument("config", type=Path)
58+
parser.add_argument(
59+
"--set",
60+
action="append",
61+
dest="overrides",
62+
required=True,
63+
metavar="KEY=VALUE",
64+
)
65+
args = parser.parse_args()
66+
try:
67+
apply_overrides(args.config, args.overrides)
68+
except (OSError, ValueError) as error:
69+
parser.error(str(error))
70+
return 0
71+
72+
73+
if __name__ == "__main__":
74+
raise SystemExit(main())

0 commit comments

Comments
 (0)