# scripting env python get standalone python [link](python-build-standalone ) > cpython-3.10.18+20250723-armv7-unknown-linux-gnueabihf-lto-full.tar.zst ``` morgana@iXD-BenNB:~/scripting/python$ tree -L 3 -d . ├── build │   ├── Modules │   │   ├── _blake2 │   │   ├── _ctypes │   │   ├── _decimal │   │   ├── _hacl │   │   ├── _io │   │   ├── _multiprocessing │   │   ├── _sqlite │   │   ├── _sre │   │   ├── _testinternalcapi │   │   ├── _xxtestfuzz │   │   └── cjkcodecs │   ├── Objects │   ├── Parser │   │   ├── lexer │   │   └── tokenizer │   ├── Programs │   ├── Python │   └── lib ├── install │   ├── bin │   ├── include │   │   └── python3.13 │   ├── lib │   │   ├── Tix8.4.3 │   │   ├── itcl4.2.4 │   │   ├── pkgconfig │   │   ├── python3.13 │   │   ├── tcl8 │   │   ├── tcl8.6 │   │   ├── thread2.8.9 │   │   └── tk8.6 │   └── share │   ├── man │   └── terminfo └── licenses ``` ## python libs/venv with repo * uv 無法直接選 image as python executable * 所以 需要建立 uv image ```dockerfile # Use a base Debian image that *does* have armv7 support. # python:3.x-slim-bookworm is a good choice as it's small and already has Python. # If you prefer a truly minimal base without pre-installed Python, # you could use 'debian:bookworm-slim' directly and then install Python via uv's `uv python install`. FROM debian:bookworm-slim AS builder # Or python:3.10-slim-bookworm, etc. Choose your desired Python version. # --- Stage 1: Install uv --- # The installer requires curl (and certificates) to download the release archive RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates # Download the latest installer ADD https://astral.sh/uv/install.sh /uv-installer.sh # Run the installer then remove it RUN sh /uv-installer.sh && rm /uv-installer.sh # Ensure the installed binary is on the `PATH` ENV PATH="/root/.local/bin/:$PATH" # Set working directory for your application WORKDIR /app # (Optional) Set environment variables for uv, as recommended by Astral for better # caching and behavior within Docker. ENV UV_COMPILE_BYTECODE=1 # Important for mounted volumes to prevent issues with symlinks ENV UV_LINK_MODE=copy # --- Install build tools (COMPILERS!) --- # This is the crucial addition for building packages like NumPy. RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ pkg-config \ libffi-dev \ patchelf \ # Add any other specific libraries NumPy/SciPy/etc. might need, e.g., libatlas-base-dev, libblas-dev, liblapack-dev # For a general build environment, build-essential is usually the primary one. && rm -rf /var/lib/apt/lists/* # --- Stage 2 (Optional but Recommended for smaller final images) --- # For this specific cross-compilation use case, we might not need a separate # runtime stage for the *builder* image itself, as its primary purpose is just # to run uv. But generally, multi-stage builds are good practice. # For simplicity in this "builder" image, we'll keep it as one stage. # The final deployable artifact is the VENV itself, not this Docker image. ``` build your own image 是必要的,官方沒有 armv7 platform image,所以要從 root base image 自己建立。 另 compiler 對後續 e.g. numpy 是必要的 ```bash docker buildx build --platform linux/arm/v7 -t uv-cross-arch-builder:armv7 . --load ``` may not need to create image, directly use `astral/uv:bookworm-slim` ```bash # Define host paths ARMV7_PYTHON_TOP_LEVEL_HOST_PATH="/home/morgana/scripting/python/" # This is the extracted 'python' directory (e.g., from the tar.xz, it's the folder containing 'bin/') APP_DIR_HOST_PATH="/home/morgana/esc_decode/" # Define a host path for the uv cache UV_CACHE_DIR_HOST_PATH="${APP_DIR_HOST_PATH}/.cache/uv-armv7" # Or any other path you prefer # Define container paths ARMV7_PYTHON_TOP_LEVEL_CONTAINER_PATH="/mnt/armv7-python-root" APP_DIR_CONTAINER_PATH="/app" # This matches the WORKDIR in the Dockerfile # Define the container path for the uv cache UV_CACHE_DIR_CONTAINER_PATH="/mnt/uv-cache" # Create the host cache directory if it doesn't exist mkdir -p "${UV_CACHE_DIR_HOST_PATH}" docker run \ -it \ --rm \ --platform linux/arm/v7 \ -v "${ARMV7_PYTHON_TOP_LEVEL_HOST_PATH}:${ARMV7_PYTHON_TOP_LEVEL_CONTAINER_PATH}:ro" \ -v "${APP_DIR_HOST_PATH}:${APP_DIR_CONTAINER_PATH}" \ -v "${UV_CACHE_DIR_HOST_PATH}:${UV_CACHE_DIR_CONTAINER_PATH}" \ uv-cross-arch-builder:armv7 \ bash -c " \ # Define the actual path to the Python executable within the mounted volume REAL_ARMV7_PYTHON_PATH=\"${ARMV7_PYTHON_TOP_LEVEL_CONTAINER_PATH}/install/bin/python3\" && \ # Define where we want to create the symlink SYMLINK_TARGET_PATH=\"/usr/local/bin/python3-armv7\" && \ \ # Export the UV_CACHE_DIR environment variable to the container shell export UV_CACHE_DIR=\"${UV_CACHE_DIR_CONTAINER_PATH}\" && \ \ echo 'Creating symbolic link for ARMv7 Python...' && \ # Create the symlink. Using -f to force overwrite if it exists. ln -sf \"\${REAL_ARMV7_PYTHON_PATH}\" \"\${SYMLINK_TARGET_PATH}\" && \ \ echo 'Verifying mounted ARMv7 Python executable via symlink...' && \ \"\${SYMLINK_TARGET_PATH}\" -c 'import sys; print(f\"Python Executable: {sys.executable}\"); print(f\"Platform: {sys.platform}\"); print(f\"Version: {sys.version}\"); print(f\"Architecture: {sys.version.split(\" \")[-1]}\")' && \ \ echo 'Creating ARMv7 virtual environment using uv...' && \ uv venv --relocatable --link-mode copy --python \"\${SYMLINK_TARGET_PATH}\" ${APP_DIR_CONTAINER_PATH}/.venv && \ \ echo 'Navigating to application directory and activating venv...' && \ cd ${APP_DIR_CONTAINER_PATH} && \ source .venv/bin/activate && \ \ echo 'Synchronizing dependencies with uv...' && \ uv sync --no-dev --active --no-editable \ " ``` no-dev --> 不 copy ruff active --> 保證所處理 的 venv no-editable --> copy 目前repo to venv。 加入 cache, 第一次emulate 編譯 numpy 維持要 28min,第二次變 8.5sec。 create tar.gz and copy ```bash tar zcvf .venv.tar.gz ./.venv/ scp ./.venv.tar.gz root@192.168.29.79:/root/scripting/esc_decode_env ``` on target machine ```bash cd /root/scripting/esc_decode_env tar zxvf .venv.tar.gz ``` ### ash version sadly `busybox ash` is not `sh`, `source` command not work, and lots of script need to modify ```shell cd /root/scripting/esc_decode_env vim .venv/bin/activate.ash ``` `activate.ash` ```shell # BusyBox ash-compatible Python virtual environment activation script # Usage: . /path/to/venv/bin/activate # Save old PATH if [ -n "$PATH" ]; then OLD_VIRTUAL_PATH="$PATH" fi deactivate() { # Restore old PATH if [ -n "$OLD_VIRTUAL_PATH" ]; then PATH="$OLD_VIRTUAL_PATH" export PATH unset OLD_VIRTUAL_PATH fi # Restore old PYTHONHOME if [ -n "$OLD_VIRTUAL_PYTHONHOME" ]; then PYTHONHOME="$OLD_VIRTUAL_PYTHONHOME" export PYTHONHOME unset OLD_VIRTUAL_PYTHONHOME fi # Restore prompt if [ -n "$OLD_VIRTUAL_PS1" ]; then PS1="$OLD_VIRTUAL_PS1" export PS1 unset OLD_VIRTUAL_PS1 fi unset VIRTUAL_ENV unset VIRTUAL_ENV_PROMPT # Remove deactivate function unless called as "deactivate nondestructive" if [ ! "$1" = "nondestructive" ]; then unset deactivate fi } # Unset irrelevant variables (safe to call even if not set) deactivate nondestructive # Set VIRTUAL_ENV to your venv's full path: VIRTUAL_ENV="$PWD/.venv" export VIRTUAL_ENV # Update PATH PATH="$VIRTUAL_ENV/bin:$PATH" export PATH # Set prompt label (change "venv" to your preferred name) VIRTUAL_ENV_PROMPT="venv" export VIRTUAL_ENV_PROMPT # Save and set PS1 if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ]; then OLD_VIRTUAL_PS1="${PS1-}" PS1="($VIRTUAL_ENV_PROMPT) ${PS1-}" export PS1 fi # Unset PYTHONHOME if set if [ -n "$PYTHONHOME" ]; then OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME" unset PYTHONHOME fi # Remove any pydoc alias unalias pydoc 2>/dev/null || true # Define pydoc function pydoc() { python -m pydoc "$@" } # Forget cached commands hash -r 2>/dev/null ``` then, modify `pyenv.cfg` ```bash vim .venv/pyvenv.cfg ``` ```ini #home = /mnt/armv7-python-root/install/bin home = /root/scripting/python/install/bin implementation = CPython uv = 0.8.3 version_info = 3.13.5 include-system-site-packages = false relocatable = true ``` ```bash # On your SBC, navigate to the directory where the venv is located cd /root/scripting/esc_decode_env ln -sf /root/scripting/python/install/bin/python3.13 .venv/bin/python # --- Now, activate the virtual environment --- VENV_PATH="/root/scripting/esc_decode_env/.venv" #source "$VENV_PATH/bin/activate" . "$VENV_PATH/bin/activate.ash" # Verify (optional) which python python --version python -m ensurepip python -m pip list deactivate ``` 基本速度測試 ```bash root@buildroot-tx7:~/scripting# time $VENV_PATH/bin/python -c "print('hello world')" hello world real 0m 0.24s ``` > 想要 another version `activate.ash`, 可以指定直接指定 full path,但一直失敗。可能`ash`就是不支援。 #### bonus nuitka build ```bash # Define host paths ARMV7_PYTHON_TOP_LEVEL_HOST_PATH="/home/morgana/scripting/python/" # This is the extracted 'python' directory (e.g., from the tar.xz, it's the folder containing 'bin/') APP_DIR_HOST_PATH="/home/morgana/esc_decode/" # Define a host path for the uv cache UV_CACHE_DIR_HOST_PATH="${APP_DIR_HOST_PATH}/.cache/uv-armv7" # Or any other path you prefer # Define container paths ARMV7_PYTHON_TOP_LEVEL_CONTAINER_PATH="/mnt/armv7-python-root" APP_DIR_CONTAINER_PATH="/app" # This matches the WORKDIR in the Dockerfile # Define the container path for the uv cache UV_CACHE_DIR_CONTAINER_PATH="/mnt/uv-cache" # Create the host cache directory if it doesn't exist mkdir -p "${UV_CACHE_DIR_HOST_PATH}" docker run \ -it \ --rm \ --platform linux/arm/v7 \ -v "${ARMV7_PYTHON_TOP_LEVEL_HOST_PATH}:${ARMV7_PYTHON_TOP_LEVEL_CONTAINER_PATH}:ro" \ -v "${APP_DIR_HOST_PATH}:${APP_DIR_CONTAINER_PATH}" \ -v "${UV_CACHE_DIR_HOST_PATH}:${UV_CACHE_DIR_CONTAINER_PATH}" \ uv-cross-arch-builder:armv7 \ bash -c " \ echo 'Navigating to application directory and activating venv...' && \ cd ${APP_DIR_CONTAINER_PATH} && \ source .venv/bin/activate && \ \ echo 'Synchronizing dependencies with uv...' && \ uv run --active python -m nuitka --onefile --standalone --static-libpython=auto --assume-yes-for-downloads esc_decode \ " ``` ### 中場 `ash` 太不可靠了,要其他方法。 如果還是想 follow venv, 1.用 bash 2.用 python 啟動腳本。 或直接指定 full python path。 `/root/scripting/esc_decode_env/.venv/bin/python -m pip list` `/root/scripting/esc_decode_env/.venv/bin/python -c "help('modules')"` ## python program 我覺得以 module(資料夾) 為主比較好。 * 資料夾名 == module name * 強制 `__init__.py` * 強制 `__main__.py` 放在主邏輯 但 uv 的 邏輯不同 * [uv Creating projects](https://docs.astral.sh/uv/concepts/projects/init/) --- `uv build --wheel --sdist` `sdist` 包含 `tests` 之類不必要的東西,所以用 `uv build --wheel`。 ## difficulty * x86 laptop 0.24s 的 program,armv7 SBC(tx7) 要 4.6s 才跑得完 * 已知問題在import,import numpy 要 1.6 sec,其他稍大的 lib 也一樣,連 pydantic 都是。 * 先 check 是 file IO or CPU。 * 以 nuitka 編譯後是 8.83s * 所以是 CPU,可能是 dynamic create numerous object。 * solving 1. 忍受 break Specification * 不可縮競爭力 2. 要求限縮 libs,e.g. 禁止 numpy。 * 不現實,用 python 就是為了各種 libs。 3. 換語言(scripting lang 八成差不多) * script lang: lua, [nodejs](https://nodejs.org/zh-tw/blog/release/v22.18.0)。 * 考量生態大小,nodejs 比較實際。 * js 是在性能上有 25x python。 * nodejs simple console.log hello world 要 1.14s,可能是 JIT 反而降低start uptime (compare python 0.25s)。 * 加 `--jitless` 還是一樣慢。 * lua 的 async 蠻差的,不建議。除非不需要類似 async 的機制。 * 前20大語言裡,應該沒有可用的 dynamic language 了。這表示,一定要用 compile language 做 scripting。 4. 放棄 language scripting e.g. 只 support gcode。 * 原地踏步,也是縮了競爭力 * 4>1>**2**>**3** ## TODO ### rejected * uv cross compile * uv sync 有 `--python-platform python-platform` * 沒有 armv7 *