diff --git a/pyproject.toml b/pyproject.toml index 0738491..141a962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ members = [ "libyuv", "nanosvg", "ncurses", + "qt5", "raylib", "zeromq", "zstd", diff --git a/qt5/build.sh b/qt5/build.sh new file mode 100755 index 0000000..1e62883 --- /dev/null +++ b/qt5/build.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$DIR" + +QT_VERSION="5.15.18" +QT_TAG="v${QT_VERSION}-lts-lgpl" +INSTALL_DIR="$DIR/qt5/install" +NJOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)" + +# Install build dependencies +if [[ "$(uname)" == "Linux" ]]; then + if command -v dnf &>/dev/null; then + dnf install -y \ + mesa-libGL-devel \ + fontconfig-devel \ + freetype-devel \ + libxcb-devel \ + xcb-util-devel \ + xcb-util-image-devel \ + xcb-util-keysyms-devel \ + xcb-util-renderutil-devel \ + xcb-util-wm-devel \ + libxkbcommon-devel \ + libxkbcommon-x11-devel \ + libX11-devel \ + perl-IPC-Cmd + elif command -v apt-get &>/dev/null; then + if [ "$(id -u)" -eq 0 ]; then + apt-get update && apt-get install -y \ + libgl-dev \ + libfontconfig1-dev libfreetype-dev \ + libxcb1-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-render-util0-dev \ + libxcb-xinerama0-dev libxcb-xkb-dev \ + libxkbcommon-dev libxkbcommon-x11-dev \ + libx11-xcb-dev + else + sudo apt-get update && sudo apt-get install -y \ + libgl-dev \ + libfontconfig1-dev libfreetype-dev \ + libxcb1-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-render-util0-dev \ + libxcb-xinerama0-dev libxcb-xkb-dev \ + libxkbcommon-dev libxkbcommon-x11-dev \ + libx11-xcb-dev + fi + fi +fi + +# Clone/update qtbase +if [ ! -d "qtbase-src/.git" ]; then + rm -rf qtbase-src + git clone --depth 1 https://code.qt.io/qt/qtbase.git qtbase-src +fi +git -C qtbase-src fetch --depth 1 origin "$QT_TAG" +git -C qtbase-src checkout --force FETCH_HEAD + +# Build qtbase +cd qtbase-src +./configure \ + -release \ + -prefix "$INSTALL_DIR" \ + -opensource -confirm-license \ + -nomake examples \ + -nomake tests \ + -no-dbus \ + -no-icu \ + -opengl desktop +make -j"$NJOBS" +make install +cd "$DIR" + +# Clone/update qtcharts +if [ ! -d "qtcharts-src/.git" ]; then + rm -rf qtcharts-src + git clone --depth 1 https://code.qt.io/qt/qtcharts.git qtcharts-src +fi +git -C qtcharts-src fetch --depth 1 origin "$QT_TAG" +git -C qtcharts-src checkout --force FETCH_HEAD + +# Build qtcharts +cd qtcharts-src +"$INSTALL_DIR/bin/qmake" +make -j"$NJOBS" +make install +cd "$DIR" + +# Replace symlinks with copies for wheel compatibility +find "$INSTALL_DIR" -type l | while read -r link; do + target="$(readlink -f "$link")" + if [ -f "$target" ]; then + rm "$link" + cp "$target" "$link" + elif [ -d "$target" ]; then + rm "$link" + cp -r "$target" "$link" + fi +done + +# Strip binaries and libraries +find "$INSTALL_DIR" -type f -name '*.so*' -exec strip --strip-unneeded {} + 2>/dev/null || true +find "$INSTALL_DIR" -type f -name '*.dylib' -exec strip -x {} + 2>/dev/null || true +strip "$INSTALL_DIR/bin/moc" "$INSTALL_DIR/bin/rcc" "$INSTALL_DIR/bin/uic" 2>/dev/null || true + +# Remove unnecessary files to reduce wheel size +rm -rf "$INSTALL_DIR/doc" "$INSTALL_DIR/mkspecs" "$INSTALL_DIR/lib/cmake" "$INSTALL_DIR/lib/pkgconfig" +find "$INSTALL_DIR/lib" -name '*.prl' -delete 2>/dev/null || true +find "$INSTALL_DIR/lib" -name '*.la' -delete 2>/dev/null || true + +echo "Installed Qt5 to $INSTALL_DIR" +du -sh "$INSTALL_DIR" diff --git a/qt5/pyproject.toml b/qt5/pyproject.toml new file mode 100644 index 0000000..55df8ff --- /dev/null +++ b/qt5/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "qt5" +version = "5.15.18" +description = "Qt 5.15 (QtBase + QtCharts)" +requires-python = ">=3.8" + +[tool.setuptools.packages.find] +include = ["qt5*"] + +[tool.setuptools.package-data] +qt5 = ["install/**/*"] diff --git a/qt5/qt5/__init__.py b/qt5/qt5/__init__.py new file mode 100644 index 0000000..0608608 --- /dev/null +++ b/qt5/qt5/__init__.py @@ -0,0 +1,19 @@ +import os + +DIR = os.path.join(os.path.dirname(__file__), "install") +BIN_DIR = os.path.join(DIR, "bin") +LIB_DIR = os.path.join(DIR, "lib") +INCLUDE_DIR = os.path.join(DIR, "include") + + +def smoketest(): + assert os.path.isfile(os.path.join(BIN_DIR, "moc")), "moc not found" + assert os.path.isfile(os.path.join(BIN_DIR, "rcc")), "rcc not found" + import glob + import platform + if platform.system() == "Darwin": + assert os.path.isdir(os.path.join(LIB_DIR, "QtCore.framework")), "QtCore.framework not found" + assert os.path.isdir(os.path.join(LIB_DIR, "QtCharts.framework")), "QtCharts.framework not found" + else: + assert glob.glob(os.path.join(LIB_DIR, "libQt5Core.so*")), "libQt5Core.so not found" + assert glob.glob(os.path.join(LIB_DIR, "libQt5Charts.so*")), "libQt5Charts.so not found" diff --git a/qt5/setup.py b/qt5/setup.py new file mode 100644 index 0000000..d9714da --- /dev/null +++ b/qt5/setup.py @@ -0,0 +1,58 @@ +import os +import platform +import subprocess + +from setuptools.command.build_py import build_py + +try: + from wheel.bdist_wheel import bdist_wheel +except ImportError: + bdist_wheel = None + + +class BuildQt5(build_py): + """Run build.sh to compile Qt5 before collecting package data.""" + + def run(self): + pkg_dir = os.path.dirname(os.path.abspath(__file__)) + build_script = os.path.join(pkg_dir, "build.sh") + subprocess.check_call(["bash", build_script], cwd=pkg_dir) + + super().run() + + +cmdclass = {"build_py": BuildQt5} + +if bdist_wheel is not None: + + class PlatformWheel(bdist_wheel): + """Produce a platform-specific, Python-version-agnostic wheel.""" + + def finalize_options(self): + super().finalize_options() + self.root_is_pure = False + + def get_tag(self): + system = platform.system() + machine = platform.machine() + + if system == "Linux": + plat = f"linux_{machine}" + elif system == "Darwin": + plat = "macosx_11_0_arm64" + else: + plat = f"{system.lower()}_{machine}" + + return "py3", "none", plat + + cmdclass["bdist_wheel"] = PlatformWheel + + +def setup(): + from setuptools import setup as _setup + + _setup(cmdclass=cmdclass) + + +if __name__ == "__main__": + setup()