As always, don’t run random scripts you find online without reading them first.
This script works for both Jetson Nano and Jetson XAvier NX to compile and install QT 15.5.2:
#!/usr/bin/env bash
set -euo pipefail
# ---- Config (override via env or flags) --------------------------------------
QT_VERSION="${QT_VERSION:-5.15.2}"
QT_PREFIX="${QT_PREFIX:-/usr/local/Qt-${QT_VERSION}}"
# For Python 3.10+, use PySide2 5.15.2.1 (5.15.2 tag hard-rejects 3.10)
PYSIDE_VERSION="${PYSIDE_VERSION:-5.15.2.1}"
QT_FLAVOR="${QT_FLAVOR:-qtbase}" # qtbase | full
JOBS="${JOBS:-$(nproc)}"
WORKDIR="${WORKDIR:-$PWD/build-qt-pyside}"
VENV_ACTIVATE="${VENV_ACTIVATE:-$HOME/.venv/bin/activate}"
QT_CONFIGURE_ARGS="${QT_CONFIGURE_ARGS:-}" # extra args if you want
# -----------------------------------------------------------------------------
usage() {
cat <<EOF
Usage: $0 [--qtbase|--full] [--qt-version X] [--pyside-version X] [--prefix PATH] [--workdir PATH] [--jobs N] [--venv-activate PATH]
Examples:
$0 --qtbase --prefix /mnt/data/Qt-5.15.2
$0 --full --qt-version 5.15.2 --pyside-version 5.15.2.1 --prefix /usr/local/Qt-5.15.2
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--qtbase) QT_FLAVOR="qtbase"; shift ;;
--full) QT_FLAVOR="full"; shift ;;
--qt-version) QT_VERSION="$2"; shift 2 ;;
--pyside-version) PYSIDE_VERSION="$2"; shift 2 ;;
--prefix) QT_PREFIX="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--jobs) JOBS="$2"; shift 2 ;;
--venv-activate) VENV_ACTIVATE="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown arg: $1"; usage; exit 1 ;;
esac
done
mkdir -p "$WORKDIR"
cd "$WORKDIR"
echo "==> Activating venv: $VENV_ACTIVATE"
if [[ ! -f "$VENV_ACTIVATE" ]]; then
echo "ERROR: venv activate script not found at: $VENV_ACTIVATE"
echo "Create it first, e.g.: pyenv local 3.10.13 && python -m venv ~/.venv"
exit 1
fi
# shellcheck disable=SC1090
source "$VENV_ACTIVATE"
echo "==> Venv Python: $(python -V)"
python - <<'PY'
import sys
maj, minor = sys.version_info[:2]
if not ((3,5) <= (maj, minor) <= (3,10)):
raise SystemExit(f"ERROR: This script targets PySide2/Qt5 builds best with Python 3.5–3.10. You have {sys.version.split()[0]}")
PY
# Ensure Python headers are available (pyenv builds usually include them)
PY_INC="$(python -c 'import sysconfig; print(sysconfig.get_config_var("INCLUDEPY") or "")')"
if [[ -z "$PY_INC" || ! -f "$PY_INC/Python.h" ]]; then
echo "ERROR: Python development headers not found for $(python -V)."
echo "If using pyenv, ensure your Python was built with headers (it normally is)."
exit 1
fi
echo "==> Installing build dependencies (Debian/Ubuntu apt)..."
sudo apt-get update -y
sudo apt-get install -y \
build-essential git wget perl pkg-config cmake ninja-build \
libgl1-mesa-dev libegl1-mesa-dev \
libfontconfig1-dev libfreetype6-dev \
libx11-dev libx11-xcb-dev libxext-dev libxrender-dev libxi-dev libxrandr-dev libxcursor-dev \
libxcomposite-dev libxdamage-dev libxtst-dev libdbus-1-dev \
libxau-dev libxdmcp-dev xcb-proto \
libxcb1-dev libxcb-util0-dev \
libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev \
libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev \
libxcb-render0-dev libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-xkb-dev libxcb-cursor-dev \
libxkbcommon-dev libxkbcommon-x11-dev
# clang/llvm/libclang: try versioned first, fallback to defaults
if apt-cache show clang-10 >/dev/null 2>&1; then
sudo apt-get install -y clang-10 llvm-10 llvm-10-dev libclang-10-dev
elif apt-cache show libclang-dev >/dev/null 2>&1; then
sudo apt-get install -y clang llvm llvm-dev libclang-dev
else
echo "WARNING: Could not find clang/llvm packages via apt-cache; install libclang+llvm manually if PySide build later complains."
fi
echo "==> Sanity check: pkg-config visibility for XCB bits..."
missing=0
for m in xcb xcb-icccm xcb-image xcb-keysyms xcb-randr xcb-render xcb-renderutil xcb-shape xcb-shm xcb-sync xcb-xfixes xcb-xinerama xcb-xkb xcb-cursor xkbcommon-x11; do
if pkg-config --exists "$m"; then
echo " OK $m"
else
echo " MISS $m"
missing=1
fi
done
if [[ "$missing" -ne 0 ]]; then
echo "ERROR: Missing one or more required XCB pkg-config modules above. Install the corresponding -dev packages and rerun."
exit 1
fi
echo "==> Downloading Qt ${QT_VERSION} (${QT_FLAVOR})..."
QT_BASE_URL="https://master.qt.io/archive/qt/5.15/${QT_VERSION}"
if [[ "$QT_FLAVOR" == "full" ]]; then
QT_TARBALL="qt-everywhere-src-${QT_VERSION}.tar.xz"
QT_URL="${QT_BASE_URL}/single/${QT_TARBALL}"
QT_SRC_DIR="qt-everywhere-src-${QT_VERSION}"
else
QT_TARBALL="qtbase-everywhere-src-${QT_VERSION}.tar.xz"
QT_URL="${QT_BASE_URL}/submodules/${QT_TARBALL}"
QT_SRC_DIR="qtbase-everywhere-src-${QT_VERSION}"
fi
wget -c "$QT_URL" -O "$QT_TARBALL"
echo "==> Extracting Qt sources..."
rm -rf "$QT_SRC_DIR"
tar -xpf "$QT_TARBALL"
echo "==> Configuring Qt (enabling xcb) ..."
# Out-of-source build (easy to clean: rm -rf build-qt)
rm -rf build-qt
mkdir -p build-qt
cd build-qt
"../${QT_SRC_DIR}/configure" \
-prefix "$QT_PREFIX" \
-opensource -confirm-license \
-xcb \
$QT_CONFIGURE_ARGS
echo "==> Building Qt (jobs=${JOBS})..."
make -j"$JOBS"
echo "==> Installing Qt to ${QT_PREFIX}..."
# Use sudo only if needed
if mkdir -p "$QT_PREFIX" 2>/dev/null && touch "$QT_PREFIX/.writable_test" 2>/dev/null; then
rm -f "$QT_PREFIX/.writable_test"
make install
else
sudo make install
fi
echo "==> Qt installed. Checking qmake..."
QMAKE="${QT_PREFIX}/bin/qmake"
if [[ ! -x "$QMAKE" && -x "${QT_PREFIX}/bin/qmake-qt5" ]]; then
QMAKE="${QT_PREFIX}/bin/qmake-qt5"
fi
if [[ ! -x "$QMAKE" ]]; then
echo "ERROR: qmake not found under ${QT_PREFIX}/bin/"
exit 1
fi
echo "==> Using qmake: $QMAKE"
"$QMAKE" -v || true
echo "==> Preparing venv tooling for PySide2 build..."
python -m pip install --upgrade pip setuptools
# Avoid the 'safer_name' wheel override noise / breakage with newer wheel
python -m pip install --upgrade "wheel<0.46"
# Help pyside find LLVM/Clang (best-effort)
if command -v llvm-config-10 >/dev/null 2>&1; then
export LLVM_INSTALL_DIR="$(llvm-config-10 --prefix)"
elif command -v llvm-config >/dev/null 2>&1; then
export LLVM_INSTALL_DIR="$(llvm-config --prefix)"
fi
cd "$WORKDIR"
mkdir -p src
if [[ ! -d "src/pyside-setup" ]]; then
echo "==> Cloning pyside-setup into $WORKDIR/src..."
git clone https://code.qt.io/pyside/pyside-setup.git "src/pyside-setup"
fi
cd "src/pyside-setup"
echo "==> Checking out PySide tag/version: ${PYSIDE_VERSION}..."
git fetch --tags
git checkout "v${PYSIDE_VERSION}" 2>/dev/null || git checkout "${PYSIDE_VERSION}"
echo "==> Cleaning old PySide build artifacts..."
rm -rf build */build pyside2-build shiboken2-build out .venv3_build .venv3_install
echo "==> Applying shiboken2 patch for Python 3.10 (_Py_Mangle)..."
python - <<'PY'
import pathlib, re, sys
p = pathlib.Path("sources/shiboken2/libshiboken/pep384impl.cpp")
if not p.exists():
sys.exit("ERROR: expected file not found: sources/shiboken2/libshiboken/pep384impl.cpp")
s = p.read_text()
# If already patched, do nothing.
if "_Py_Mangle" in s and re.search(r'^\s*#\s*ifdef\s+IS_PY2\s*$', s, re.M):
print(" Patch already applied.")
sys.exit(0)
# Replace the guard right above the _Py_Mangle return with Python2-only.
s2 = re.sub(
r'(#\s*)ifndef\s+Py_LIMITED_API(\s*\n\s*return\s+_Py_Mangle\s*\()',
r'\1ifdef IS_PY2\2',
s,
count=1
)
if s2 == s:
sys.exit("ERROR: Could not apply patch automatically. Search for '_Py_Mangle' and change the '#ifndef Py_LIMITED_API' above it to '#ifdef IS_PY2'.")
p.write_text(s2)
print(" Patched:", p)
PY
echo "==> Building + installing PySide2 into the venv (no sudo)..."
# 'jobs' is deprecated; use --parallel
python setup.py install --qmake="$QMAKE" --parallel "$JOBS"
echo "==> Verifying PySide2 import inside venv..."
python -c "import PySide2; print('PySide2:', PySide2.__version__)"
echo "==> Done."
echo "Qt: $QT_PREFIX"
echo "qmake: $QMAKE"
echo "PySide2: installed into venv at: $(python -c 'import sys; print(sys.prefix)')"