From 3146cab449b0450bb70877083e690ab3d0458e8a Mon Sep 17 00:00:00 2001 From: pc-heini Date: Tue, 16 Jun 2026 02:41:20 +0200 Subject: [PATCH] Fix o3de CLI install on read-only /app via prebuilt sdist The real first-launch installer is python/get_python.sh, not LYPython.cmake. It does `pip install -e scripts/o3de`, whose egg_info write hits read-only /app and fails ([Errno 30]). get_python.sh has a built-in immutable-install hook: if scripts/o3de/dist/o3de-1.0.0.tar.gz exists it installs that (non-editable) instead. Pre-build that tarball at Flatpak build time (python3 setup.py sdist) so the runtime CLI lands in the writable ~/.o3de venv. Verified end-to-end: tarball install -> `import o3de` resolves from site-packages. Also patch every LYPython.cmake copy (not just the first) for the separate project-build CMake-configure path, and add python3/python3-setuptools to CI. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/build-flatpak.yml | 1 + README.md | 23 ++++++++++------ org.o3de.O3DE.yaml | 25 +++++++++++++----- scripts/make-flatpak.sh | 42 +++++++++++++++++++++--------- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/.gitea/workflows/build-flatpak.yml b/.gitea/workflows/build-flatpak.yml index 1227caa6..36c1af6b 100644 --- a/.gitea/workflows/build-flatpak.yml +++ b/.gitea/workflows/build-flatpak.yml @@ -28,6 +28,7 @@ jobs: apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ ca-certificates curl git jq xz-utils zstd binutils tar \ + python3 python3-setuptools \ flatpak # Done as a plain git clone instead of actions/checkout@v4: the bare diff --git a/README.md b/README.md index ebc06f90..f7cdcd1e 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,8 @@ the upstream version has no matching `vX.Y.Z` tag yet, so reruns are cheap no-op ## Building locally ```sh -# flatpak + cmake (cmake is used by O3DE's get_python.sh) + curl -sudo apt install flatpak cmake # or your distro's equivalent +# flatpak + curl + python3/setuptools (to pre-build the o3de sdist) +sudo apt install flatpak python3-setuptools # or your distro's equivalent ./scripts/build.sh ``` @@ -131,12 +131,19 @@ These were confirmed by inspecting the v26.05 package (`opt/O3DE/26.05/…`): runtime and builds a venv under `~/.o3de` (writable), pip-installing its deps there. The build does **not** bundle Python — the venv is keyed to the user's home path, so it can only be created at runtime. -- **The editable-install patch.** O3DE installs its own `o3de` CLI with - `pip install -e`, which writes an `egg-info` next to the source under read-only - `/app` and fails. The build patches `cmake/LYPython.cmake` to drop the `-e`, so - it's a normal install (built in a temp dir, landing in the writable `~/.o3de` - venv). If a future O3DE release changes that pip command, the build prints a - warning and Python setup will fail at runtime — that's the line to re-check. +- **The o3de CLI install (read-only `/app`).** On first launch O3DE installs its + own `o3de` CLI from `scripts/o3de` into the per-user `~/.o3de` venv. By default + `python/get_python.sh` does an *editable* install (`pip install -e`), which + writes an `egg-info` next to the source under read-only `/app` and fails. + `get_python.sh` has a built-in immutable-install hook (the Snap path): if a + prebuilt sdist exists at `scripts/o3de/dist/o3de-1.0.0.tar.gz` it installs that + (non-editable) instead. The build pre-creates that tarball (`python3 setup.py + sdist`) while the tree is still writable, so at runtime the CLI lands in the + writable venv and nothing is written to `/app`. (The build also patches every + `cmake/LYPython.cmake` to a non-editable install, covering the separate + project-build CMake-configure path.) If a future release renames the package or + bumps its version away from `1.0.0`, the build fails fast — those are the lines + to re-check. - **Other runtime writes into the install tree may still fail.** Anything else O3DE tries to generate *inside* `/opt/O3DE/...` at runtime (e.g. certain per-gem Python deps, or the engine-level asset cache) hits the read-only `/app`. Project diff --git a/org.o3de.O3DE.yaml b/org.o3de.O3DE.yaml index 367d4567..ec597472 100644 --- a/org.o3de.O3DE.yaml +++ b/org.o3de.O3DE.yaml @@ -37,17 +37,28 @@ modules: # is kept for robustness in case a future release adds desktop glue there.) - 'if [ -d data/opt ]; then mkdir -p "${FLATPAK_DEST}/opt"; cp -a data/opt/. "${FLATPAK_DEST}/opt/"; fi' - 'if [ -d data/usr ]; then cp -a data/usr/. "${FLATPAK_DEST}/"; fi' - # On first launch O3DE pip-installs its 'o3de' CLI editable ('pip install - # -e'), which writes an egg-info into read-only /app and fails. Force a - # normal install (built in a temp dir, lands in the writable ~/.o3de venv). + # On first launch O3DE installs its 'o3de' CLI from scripts/o3de. By default + # python/get_python.sh does an *editable* install ('pip install -e'), which + # writes an egg-info into read-only /app and fails. get_python.sh has a + # built-in immutable-install hook (the Snap path): if a prebuilt sdist exists + # at scripts/o3de/dist/o3de-1.0.0.tar.gz it installs that (non-editable) + # instead. Build the tarball now, while /app is still writable. - | set -e - LYPYTHON=$(find "${FLATPAK_DEST}/opt/O3DE" -path '*/cmake/LYPython.cmake' | head -n1) - if [ -n "$LYPYTHON" ] && grep -qF -- '-m pip install -e ' "$LYPYTHON"; then - sed -i 's/-m pip install -e /-m pip install /g' "$LYPYTHON" + O3DE_PKG=$(find "${FLATPAK_DEST}/opt/O3DE" -maxdepth 3 -type d -path '*/scripts/o3de' | head -n1) + if [ -n "$O3DE_PKG" ] && [ -f "$O3DE_PKG/setup.py" ]; then + ( cd "$O3DE_PKG" && python3 setup.py sdist ) + test -f "$O3DE_PKG/dist/o3de-1.0.0.tar.gz" else - echo "::warning:: 'pip install -e' not found in LYPython.cmake" + echo "scripts/o3de/setup.py not found; O3DE layout may have changed" >&2 + exit 1 fi + # Belt-and-suspenders: LYPython.cmake also installs o3de (editable) during a + # project-build CMake configure. Patch every copy to non-editable. + find "${FLATPAK_DEST}/opt/O3DE" -path '*/cmake/LYPython.cmake' | while read -r LYPYTHON; do + grep -qF -- '-m pip install -e ' "$LYPYTHON" && \ + sed -i 's/-m pip install -e /-m pip install /g' "$LYPYTHON" || true + done # Launcher + AppStream + desktop entry under the Flatpak app-id. - install -Dm755 o3de-wrapper.sh "${FLATPAK_DEST}/bin/o3de-wrapper.sh" - install -Dm644 org.o3de.O3DE.desktop "${FLATPAK_DEST}/share/applications/org.o3de.O3DE.desktop" diff --git a/scripts/make-flatpak.sh b/scripts/make-flatpak.sh index c7332126..2ce9eec3 100755 --- a/scripts/make-flatpak.sh +++ b/scripts/make-flatpak.sh @@ -29,21 +29,39 @@ tar -C data -xf data.tar.* mkdir -p "$DEST/opt" cp -a data/opt/. "$DEST/opt/" -echo ">> patching the editable pip install (read-only /app workaround)" -# On first launch O3DE sets up a per-user Python venv in ~/.o3de and pip-installs -# its 'o3de' CLI with 'pip install -e' (editable). Editable mode writes an -# egg-info next to the source under /app, which is read-only in a Flatpak, so it -# fails. Force a normal (non-editable) install instead: pip builds in a temp dir -# and installs into the writable ~/.o3de venv. (Nothing Python-related needs to -# be baked into the image; it all lives per-user under ~/.o3de.) -LYPYTHON=$(find "$DEST/opt/O3DE" -path '*/cmake/LYPython.cmake' | head -n1) -if [ -n "$LYPYTHON" ] && grep -qF -- '-m pip install -e ' "$LYPYTHON"; then - sed -i 's/-m pip install -e /-m pip install /g' "$LYPYTHON" - echo " patched: $LYPYTHON" +echo ">> pre-building the o3de sdist (read-only /app workaround)" +# On first launch O3DE builds a per-user Python venv in ~/.o3de and installs its +# 'o3de' CLI from /scripts/o3de. By default python/get_python.sh does an +# *editable* install ('pip install -e'), which writes an egg-info next to the +# source under read-only /app and fails. get_python.sh has a built-in escape hatch +# for immutable installs (the Snap path): if a prebuilt sdist exists at +# scripts/o3de/dist/o3de-1.0.0.tar.gz it installs THAT (non-editable) instead. So +# we build that tarball here, while the tree is still writable. At runtime the +# tarball is copied into the writable ~/.o3de venv - nothing is written to /app. +O3DE_PKG=$(find "$DEST/opt/O3DE" -maxdepth 3 -type d -path '*/scripts/o3de' | head -n1) +if [ -n "$O3DE_PKG" ] && [ -f "$O3DE_PKG/setup.py" ]; then + ( cd "$O3DE_PKG" && python3 setup.py sdist ) + if [ -f "$O3DE_PKG/dist/o3de-1.0.0.tar.gz" ]; then + echo " built: $O3DE_PKG/dist/o3de-1.0.0.tar.gz" + else + echo " ERROR: sdist did not produce o3de-1.0.0.tar.gz" >&2 + exit 1 + fi else - echo " WARNING: 'pip install -e' not found in LYPython.cmake; O3DE layout may have changed" >&2 + echo " ERROR: scripts/o3de/setup.py not found; O3DE layout may have changed" >&2 + exit 1 fi +# Belt-and-suspenders for the project-build path: LYPython.cmake also installs the +# o3de package (editable) during a CMake configure when you build a project. Patch +# every copy to a non-editable install so that write doesn't hit read-only /app. +find "$DEST/opt/O3DE" -path '*/cmake/LYPython.cmake' | while read -r LYPYTHON; do + if grep -qF -- '-m pip install -e ' "$LYPYTHON"; then + sed -i 's/-m pip install -e /-m pip install /g' "$LYPYTHON" + echo " patched: $LYPYTHON" + fi +done + echo ">> installing launcher + metadata" install -Dm755 o3de-wrapper.sh "$DEST/bin/o3de-wrapper.sh" install -Dm644 org.o3de.O3DE.desktop "$DEST/share/applications/$APP_ID.desktop"