From 1f1d7848f9c73dcdbb0e6092e0f353bd3e97bc07 Mon Sep 17 00:00:00 2001 From: Ivan Mincik Date: Thu, 22 Aug 2024 12:59:25 +0200 Subject: [PATCH] nix: add nix packaging files --- flake.lock | 57 +++++++++++ flake.nix | 81 +++++++++++++++ package.nix | 40 ++++++++ set-pyqt-package-dirs.patch | 49 +++++++++ unwrapped.nix | 196 ++++++++++++++++++++++++++++++++++++ 5 files changed, 423 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 package.nix create mode 100644 set-pyqt-package-dirs.patch create mode 100644 unwrapped.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000000..0d8fc5f027ed --- /dev/null +++ b/flake.lock @@ -0,0 +1,57 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1722555600, + "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1724271409, + "narHash": "sha256-z4nw9HxkaXEn+5OT8ljLVL2oataHvAzUQ1LEi8Fp+SY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "36a9aeaaa17a2d4348498275f9fe530cd4f9e519", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1722555339, + "narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000000..a810c63435b5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,81 @@ +{ + description = "QGIS"; + + nixConfig = { + bash-prompt = "\\[\\033[1m\\][qgis-dev]\\[\\033\[m\\]\\040\\w >\\040"; + }; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + + systems = [ "x86_64-linux" ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: { + + packages = rec { + qgis-unwrapped = pkgs.libsForQt5.callPackage ./unwrapped.nix { }; + qgis = pkgs.callPackage ./package.nix { qgis-unwrapped = qgis-unwrapped; }; + }; + + devShells.default = + let + pyPackages = pkgs.python311; + + py = pyPackages.override { + self = py; + packageOverrides = self: super: { + pyqt5 = super.pyqt5.override { + withLocation = true; + withSerialPort = true; + }; + }; + }; + + pyqtPatch = pkgs.substituteAll { + src = ./set-pyqt-package-dirs.patch; + pyQt5PackageDir = "${py.pkgs.pyqt5}/${py.pkgs.python.sitePackages}"; + qsciPackageDir = "${py.pkgs.qscintilla-qt5}/${py.pkgs.python.sitePackages}"; + }; + + in + pkgs.mkShell { + inputsFrom = [ + self'.packages.qgis-unwrapped + self'.packages.qgis + ]; + + shellHook = '' + patch -p1 < ${pyqtPatch} + + export QT_QPA_PLATFORM_PLUGIN_PATH="${pkgs.libsForQt5.qt5.qtbase}/${pkgs.libsForQt5.qt5.qtbase.qtPluginPrefix}/platforms"; + + function dev-help { + + echo -e "\nWelcome to a QGIS development environment !" + echo "Build QGIS using following commands:" + echo + echo " 1. cmake -G Ninja -D CMAKE_BUILD_TYPE=Debug -D CMAKE_INSTALL_PREFIX=\$(pwd)/app -DWITH_QTWEBKIT=OFF -DQT_PLUGINS_DIR=${pkgs.libsForQt5.qt5.qtbase}/${pkgs.libsForQt5.qt5.qtbase.qtPluginPrefix}" + echo " 2. ninja" + echo " 3. ninja install" + echo + echo "Run tests:" + echo + echo "1. ninja test" + echo + echo "Note: run 'nix flake update' from time to time to update dependencies." + echo + echo "Run 'dev-help' to see this message again." + } + + dev-help + ''; + }; + }; + + flake = { }; + }; +} diff --git a/package.nix b/package.nix new file mode 100644 index 000000000000..c2dbd53b607c --- /dev/null +++ b/package.nix @@ -0,0 +1,40 @@ +{ makeWrapper +, nixosTests +, symlinkJoin + +, extraPythonPackages ? (ps: [ ]) +, qgis-unwrapped +}: + +symlinkJoin rec { + + inherit (qgis-unwrapped) version; + name = "qgis-${version}"; + + paths = [ qgis-unwrapped ]; + + nativeBuildInputs = [ + makeWrapper + qgis-unwrapped.py.pkgs.wrapPython + ]; + + # extend to add to the python environment of QGIS without rebuilding QGIS application. + pythonInputs = qgis-unwrapped.pythonBuildInputs ++ (extraPythonPackages qgis-unwrapped.py.pkgs); + + postBuild = '' + # unpackPhase + + buildPythonPath "$pythonInputs" + + wrapProgram $out/bin/qgis \ + --prefix PATH : $program_PATH \ + --set PYTHONPATH $program_PYTHONPATH + ''; + + passthru = { + unwrapped = qgis-unwrapped; + tests.qgis = nixosTests.qgis; + }; + + meta = qgis-unwrapped.meta; +} diff --git a/set-pyqt-package-dirs.patch b/set-pyqt-package-dirs.patch new file mode 100644 index 000000000000..a1771d9f03d3 --- /dev/null +++ b/set-pyqt-package-dirs.patch @@ -0,0 +1,49 @@ +diff --git a/cmake/FindPyQt5.cmake b/cmake/FindPyQt5.cmake +index b51fd0075e..87ee317e05 100644 +--- a/cmake/FindPyQt5.cmake ++++ b/cmake/FindPyQt5.cmake +@@ -25,7 +25,7 @@ ELSE(EXISTS PYQT5_VERSION_STR) + IF(SIP_BUILD_EXECUTABLE) + # SIP >= 5.0 path + +- FILE(GLOB _pyqt5_metadata "${Python_SITEARCH}/PyQt5-*.dist-info/METADATA") ++ FILE(GLOB _pyqt5_metadata "@pyQt5PackageDir@/PyQt5-*.dist-info/METADATA") + IF(_pyqt5_metadata) + FILE(READ ${_pyqt5_metadata} _pyqt5_metadata_contents) + STRING(REGEX REPLACE ".*\nVersion: ([^\n]+).*$" "\\1" PYQT5_VERSION_STR ${_pyqt5_metadata_contents}) + +diff --git a/cmake/FindQsci.cmake b/cmake/FindQsci.cmake +index 69e41c1fe9..5456c3d59b 100644 +--- a/cmake/FindQsci.cmake ++++ b/cmake/FindQsci.cmake +@@ -24,7 +24,7 @@ ELSE(QSCI_MOD_VERSION_STR) + IF(SIP_BUILD_EXECUTABLE) + # SIP >= 5.0 path + +- FILE(GLOB _qsci_metadata "${Python_SITEARCH}/QScintilla*.dist-info/METADATA") ++ FILE(GLOB _qsci_metadata "@qsciPackageDir@/QScintilla*.dist-info/METADATA") + IF(_qsci_metadata) + FILE(READ ${_qsci_metadata} _qsci_metadata_contents) + STRING(REGEX REPLACE ".*\nVersion: ([^\n]+).*$" "\\1" QSCI_MOD_VERSION_STR ${_qsci_metadata_contents}) +@@ -33,7 +33,7 @@ ELSE(QSCI_MOD_VERSION_STR) + ENDIF(_qsci_metadata) + + IF(QSCI_MOD_VERSION_STR) +- SET(QSCI_SIP_DIR "${PYQT_SIP_DIR}") ++ SET(QSCI_SIP_DIR "@qsciPackageDir@/PyQt5/bindings") + SET(QSCI_FOUND TRUE) + ENDIF(QSCI_MOD_VERSION_STR) + +diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt +index 4cd19c3af4..668cc6a5e6 100644 +--- a/python/CMakeLists.txt ++++ b/python/CMakeLists.txt +@@ -212,7 +212,7 @@ if (WITH_GUI) + install(FILES ${QGIS_PYTHON_OUTPUT_DIRECTORY}/_gui.pyi DESTINATION ${QGIS_PYTHON_DIR}) + endif() + if(QSCI_SIP_DIR) +- set(SIP_EXTRA_OPTIONS ${SIP_EXTRA_OPTIONS} -I ${QSCI_SIP_DIR}) ++ set(SIP_BUILD_EXTRA_OPTIONS ${SIP_BUILD_EXTRA_OPTIONS} --include-dir=${QSCI_SIP_DIR}) + else() + message(STATUS "Qsci sip file not found - disabling bindings for derived classes") + set(SIP_DISABLE_FEATURES ${SIP_DISABLE_FEATURES} HAVE_QSCI_SIP) diff --git a/unwrapped.nix b/unwrapped.nix new file mode 100644 index 000000000000..81ed1a1255bf --- /dev/null +++ b/unwrapped.nix @@ -0,0 +1,196 @@ +{ lib +, makeWrapper +, mkDerivation +, substituteAll +, wrapGAppsHook3 +, wrapQtAppsHook + +, withGrass ? false +, withWebKit ? false + +, bison +, cmake +, draco +, exiv2 +, fcgi +, flex +, geos +, grass +, gsl +, hdf5 +, libspatialindex +, libspatialite +, libzip +, netcdf +, ninja +, openssl +, pdal +, postgresql +, proj +, protobuf +, python311 +, qca-qt5 +, qscintilla +, qt3d +, qtbase +, qtkeychain +, qtlocation +, qtmultimedia +, qtsensors +, qtserialport +, qtwebkit +, qtxmlpatterns +, qwt +, sqlite +, txt2tags +, zstd +}: + +let + py = python311.override { + self = py; + packageOverrides = self: super: { + pyqt5 = super.pyqt5.override { + withLocation = true; + withSerialPort = true; + }; + }; + }; + + pythonBuildInputs = with py.pkgs; [ + chardet + gdal + jinja2 + numpy + owslib + psycopg2 + pygments + pyqt5 + pyqt-builder + python-dateutil + pytz + pyyaml + qscintilla-qt5 + requests + setuptools + sip + six + urllib3 + ]; +in mkDerivation rec { + pname = "qgis-unwrapped"; + version = "dev"; + + src = lib.cleanSourceWith { + src = ./.; + filter = ( + path: type: (builtins.all (x: x != baseNameOf path) [ + ".git" + ".github" + "flake.nix" + "package.nix" + "unwrapped.nix" + ]) + ); + }; + + passthru = { + inherit pythonBuildInputs; + inherit py; + }; + + nativeBuildInputs = [ + makeWrapper + wrapGAppsHook3 + wrapQtAppsHook + + bison + cmake + flex + ninja + ]; + + buildInputs = [ + draco + exiv2 + fcgi + geos + gsl + hdf5 + libspatialindex + libspatialite + libzip + netcdf + openssl + pdal + postgresql + proj + protobuf + qca-qt5 + qscintilla + qt3d + qtbase + qtkeychain + qtlocation + qtmultimedia + qtsensors + qtserialport + qtxmlpatterns + qwt + sqlite + txt2tags + zstd + ] ++ lib.optional withGrass grass + ++ lib.optional withWebKit qtwebkit + ++ pythonBuildInputs; + + patches = [ + (substituteAll { + src = ./set-pyqt-package-dirs.patch; + pyQt5PackageDir = "${py.pkgs.pyqt5}/${py.pkgs.python.sitePackages}"; + qsciPackageDir = "${py.pkgs.qscintilla-qt5}/${py.pkgs.python.sitePackages}"; + }) + ]; + + # Add path to Qt platform plugins + # (offscreen is needed by "${APIS_SRC_DIR}/generate_console_pap.py") + env.QT_QPA_PLATFORM_PLUGIN_PATH="${qtbase}/${qtbase.qtPluginPrefix}/platforms"; + + cmakeFlags = [ + "-DCMAKE_BUILD_TYPE=Release" + "-DWITH_3D=True" + "-DWITH_PDAL=True" + "-DENABLE_TESTS=False" + "-DQT_PLUGINS_DIR=${qtbase}/${qtbase.qtPluginPrefix}" + ] ++ lib.optional (!withWebKit) "-DWITH_QTWEBKIT=OFF" + ++ lib.optional withGrass (let + gmajor = lib.versions.major grass.version; + gminor = lib.versions.minor grass.version; + in "-DGRASS_PREFIX${gmajor}=${grass}/grass${gmajor}${gminor}" + ); + + qtWrapperArgs = [ + "--set QT_QPA_PLATFORM_PLUGIN_PATH ${qtbase}/${qtbase.qtPluginPrefix}/platforms" + ]; + + dontWrapGApps = true; # wrapper params passed below + + postFixup = lib.optionalString withGrass '' + # GRASS has to be availble on the command line even though we baked in + # the path at build time using GRASS_PREFIX. + # Using wrapGAppsHook also prevents file dialogs from crashing the program + # on non-NixOS. + wrapProgram $out/bin/qgis \ + "''${gappsWrapperArgs[@]}" \ + --prefix PATH : ${lib.makeBinPath [ grass ]} + ''; + + meta = with lib; { + description = "Free and Open Source Geographic Information System"; + homepage = "https://www.qgis.org"; + license = licenses.gpl2Plus; + maintainers = with maintainers; teams.geospatial.members ++ [ lsix ]; + platforms = with platforms; linux; + mainProgram = "qgis"; + }; +}