diff --git a/README.md b/README.md index 0df5cf0f0..d9ed0629a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ curl -fsSL https://abdess.github.io/retrobios/i | sh # Windows (PowerShell) irm https://abdess.github.io/retrobios/i.ps1 | iex -# Android (Termux, after pkg install python and termux-setup-storage) +# Android (Termux: it installs Python and asks for storage access) curl -fsSL https://abdess.github.io/retrobios/i | sh # SD card mounted on a PC: a muOS, KNULLI, ROCKNIX, Onion, spruceOS or MinUI card diff --git a/install.sh b/install.sh index b66e4eb87..5f9e24b58 100755 --- a/install.sh +++ b/install.sh @@ -212,6 +212,27 @@ if [ -z "$PYTHON" ]; then exit 1 fi +# Termux reaches shared storage, where RetroArch keeps its system folder, +# once termux-setup-storage has run: the installer told the user to run it +# and start again. At a terminal the bootstrap runs it here. It only asks +# the Termux app, which shows Android's permission dialog and creates +# ~/storage once allowed: the wait gives the user time to answer. +case "${TERMUX_VERSION:-}:${PREFIX:-}" in + ?*:*|*com.termux*) + if [ ! -d "$HOME/storage" ] && command -v termux-setup-storage >/dev/null 2>&1 \ + && (: /dev/null; then + echo "Android asks for the storage permission: allow it, so that the files reach" >/dev/tty + echo "the RetroArch folder." >/dev/tty + termux-setup-storage /dev/tty 2>&1 || true + waited=0 + while [ ! -d "$HOME/storage" ] && [ "$waited" -lt 60 ]; do + sleep 1 + waited=$((waited + 1)) + done + fi + ;; +esac + # Piped from curl, standard input is this script, not the terminal: the # installer would skip every question it can ask. Hand it the terminal when # there is one. diff --git a/platforms/_guides.yml b/platforms/_guides.yml index 2a886327d..aa6bf2dd1 100644 --- a/platforms/_guides.yml +++ b/platforms/_guides.yml @@ -116,8 +116,8 @@ terminals: android: label: Android (Termux) how: >- - Install **Termux**, a terminal app. Run `pkg install python` and `termux-setup-storage` - once. Then paste the line. Press Enter. + Install **Termux**, a terminal app. Paste the line. Press Enter. Accept the Python + install and allow the storage access Android asks for. shell: sh device: label: On the device diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index aba4e9c9f..4854da14d 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -575,7 +575,7 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json" "# Windows (PowerShell)", install_command("powershell"), "", - "# Android (Termux, after pkg install python and termux-setup-storage)", + "# Android (Termux: it installs Python and asks for storage access)", install_command("sh"), "", "# SD card mounted on a PC: a muOS, KNULLI, ROCKNIX, Onion, spruceOS or MinUI card", diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 2a763163b..570a8115f 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -8471,7 +8471,7 @@ def install_commands(args: str = "", indent: str = "", for label, shell in ( ("Linux, macOS, Steam Deck:", "sh"), ("Windows (PowerShell):", "powershell"), - ("Android (Termux, after `pkg install python` and `termux-setup-storage`):", "sh"), + ("Android (Termux, the command installs Python and asks for storage access):", "sh"), ): lines += [f"{indent}{label}", "", *fence(shell, install_command(shell, args), indent), ""] return lines diff --git a/tests/test_install.py b/tests/test_install.py index c1036f4a5..ab9d932b8 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2694,6 +2694,51 @@ class WindowsWithoutPythonIsOfferedWinget(unittest.TestCase): self.assertFalse((fake_bin / "python3").exists()) +@unittest.skipUnless(shutil.which("script"), "needs a terminal") +class TermuxStorageIsSetUpByTheBootstrap(unittest.TestCase): + """The installer told an Android user to run termux-setup-storage and + start the whole run over: the bootstrap runs it and waits for ~/storage.""" + + def _run(self, storage_exists: bool) -> tuple[subprocess.CompletedProcess, Path]: + root = Path(_tempdir(self)) + shutil.copy(REPO_ROOT / "install.sh", root / "install.sh") + (root / "install.py").write_text("print('installer ran')\n") + home = root / "home" + home.mkdir() + if storage_exists: + (home / "storage").mkdir() + fake_bin = root / "bin" + fake_bin.mkdir() + for tool in ("sed", "tr", "dirname", "cat", "awk", "uname", "sleep", "mkdir"): + found = shutil.which(tool) + if found: + (fake_bin / tool).symlink_to(found) + (fake_bin / "python3").symlink_to(sys.executable) + # The app creates ~/storage once Android's dialog is answered. + (fake_bin / "termux-setup-storage").write_text( + f'#!/bin/sh\necho ran >> "{root}/setup.log"\nmkdir "$HOME/storage"\n') + (fake_bin / "termux-setup-storage").chmod(0o755) + command = (f"PATH={shlex.quote(str(fake_bin))} HOME={shlex.quote(str(home))} " + f"TERMUX_VERSION=0.118 /bin/sh {shlex.quote(str(root / 'install.sh'))}") + proc = subprocess.run( + [shutil.which("script"), "-qec", command, "/dev/null"], + capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL) + return proc, root + + def test_it_runs_once_then_the_installer(self): + proc, root = self._run(storage_exists=False) + self.assertEqual(proc.returncode, 0, proc.stdout) + self.assertEqual((root / "setup.log").read_text(), "ran\n") + self.assertIn("allow it", proc.stdout) + self.assertIn("installer ran", proc.stdout) + + def test_a_set_up_storage_is_left_alone(self): + proc, root = self._run(storage_exists=True) + self.assertEqual(proc.returncode, 0, proc.stdout) + self.assertFalse((root / "setup.log").exists()) + self.assertIn("installer ran", proc.stdout) + + @unittest.skipUnless(shutil.which("pwsh"), "needs PowerShell") class APyLauncherWithoutARuntimeIsPassed(unittest.TestCase): """findPython returned the py launcher as soon as it existed: one left diff --git a/wiki/first-install.md b/wiki/first-install.md index fab2d9241..7610621ee 100644 --- a/wiki/first-install.md +++ b/wiki/first-install.md @@ -53,11 +53,10 @@ curl -fsSL https://abdess.github.io/retrobios/i | sh -Install **Termux**, a terminal app, and run these lines. +Install **Termux**, a terminal app, and run this line. It installs Python +and asks Android for storage access: allow both. ```sh title="Terminal" -pkg install python -termux-setup-storage curl -fsSL https://abdess.github.io/retrobios/i | sh ``` diff --git a/wiki/installer.md b/wiki/installer.md index 71a289f59..b69d2c7c8 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -1397,11 +1397,12 @@ writable (Android 10 and earlier), it is a standalone copy target. Under scoped storage the two RetroArch config paths are unreadable from Termux on Android 11 and later, so on current devices the folders are what answers. -Running `termux-setup-storage` once is what makes the shared one reachable: +Running `termux-setup-storage` once is what makes the shared one reachable. +The one-line command runs it when `~/storage` is missing, then waits up to a +minute for the permission Android asks for, as it offers `pkg install python` +when Python is missing: ```bash -pkg install python -termux-setup-storage curl -fsSL https://abdess.github.io/retrobios/i | sh ```