# 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
*