Trying to package `System Monitoring Center`

There is an app called System Monitoring Center which is not packaged in nixpkgs, so I’m trying to package it myself. Here is what I have so far:
nixpkgs: c3d4ac725177c030b1e289015989da2ad9d56af0

{
    pkgs,
    stdenv,
    fetchFromGitHub,
    python3Packages,
    gobject-introspection,
    desktop-file-utils,
    gettext,
    glib,
    gtk4,
    libadwaita,
    meson,
    ninja,
    pkg-config,
    wrapGAppsHook4,
    cairo,
}:
let
    pp = python3Packages;
in
pp.buildPythonApplication {
    pname = "system-monitoring-center";
    version = "v2.26.0";

    src = fetchFromGitHub {
        # https://github.com/hakandundar34coding/system-monitoring-center/tree/4d9424833bd36cfd4fe7521139fe57faf9ee626b
        owner = "hakandundar34coding";
        repo = "system-monitoring-center";
        rev = "4d9424833bd36cfd4fe7521139fe57faf9ee626b";
        hash = "sha256-HSzQ13Y+KV8J7lzUZ4L+6NNQUIHkgF4SxPpHIKCHwt8=";
    };
    pyproject = false; # built with meson

    nativeBuildInputs = [
        meson
        ninja
        pkg-config
        wrapGAppsHook4
        desktop-file-utils
        gobject-introspection
        gettext
    ];
    buildInputs = [
        glib
        gtk4
        libadwaita
        cairo
    ];
    propagatedBuildInputs = [
        pp.pygobject3
    ];

    dontWrapGApps = true;

    preFixup = ''
        makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
    '';
}

I have no idea what dontWrapGApps and preFixup do, I just copy-pasted them from other packages to make this one build.
Miraculously, it starts up with ./result/bin/system-monitoring-center.

There are 3 problems:

  1. On “Network” and “GPU” tabs it tries to read /usr/share/hwdata/ and fails
  2. On “Processes” tab it tries to read /usr/share/applications/ and fails
  3. On “Services” tab it tries to read /lib/systemd/system/ and fails

It never crashes, just doesn’t display the info.

My question is: how can I fix this?

For issues like this, you’ll most likely need to find where those paths appear in the source files and patch them, either by using something like substituteInPlace in a postPatch script, or by making a literal patch and adding it to patches.

Hmm, I was thinking I would need something like buildFHSEnv, but apparently no

As I can see here /nix/store/l1w7palbmg70a5wz3mk01wcp3m3ajdvc-hwdata-0.384/share/hwdata/, contains just some text files.

As for /usr/share/applications/ and /lib/systemd/system/, I don’t see where I can take them from, but I’ll look better in the morning.

This is a python app and I’m a python dev :tada: I see the exact places these paths are read.

Thank you for pointing me to just patching it!
I will share whatever results I’ll get

On NixOS, the equivalent system-wide paths would be /run/current-system/sw/share/applications and /run/current-system/sw/lib/systemd/system.

If you’re hoping to use the Nix-packaged version of this app on other OSes, I wouldn’t know what to do. :person_shrugging:

Amazing! You just saved me even more time. Thanks

It should work fine on traditional FHS distros without the patch. I think, one can apply the patch only if building for nixos.
Don’t know if there is a convention on how to do this properly, but I would add something like

{ isNixOs ? true, ...}: ...

to package args. As long as It’s used just in my flake, I’ll pass it the correct value

The convention would be to not package the thing differently on nixos vs non-nixos, generally.
In nixpkgs we discourage absolute path/path string literals whenever possible, as they are liable to break under various circumstances. Instead, I’d look into why it’s trying to read those paths in the first place?

1 Like

I got it.
I patched in is_nixos function that checks /etc/os-release in runtime. This way builds for nixos and non-nixos are the same.

Now if the app finds itself on nixos:

  • it uses HWDATA_PATH env-var instead of previously hardcoded /usr/share/hwdata/. HWDATA_PATH is set by wrapper.
  • it uses /run/current-system/sw/share/applications and /run/current-system/sw/lib/systemd/system (/run/current-system/sw/ seems consistent enough to hard-code it in)

This fixed “Network”, “GPU” and “Processes” tabs.

“Services” remains broken. It ignores symlinked .service files for some reason. Even flatpak version didn’t show services when installed on nixos.
Anyway, I don’t need this tab too much.

Thank you guys for your advise!

Working package:
nixpkgs: c3d4ac725177c030b1e289015989da2ad9d56af0

default.nix:

{
    pkgs,
    stdenv,
    fetchFromGitHub,
    python3Packages,
    gobject-introspection,
    desktop-file-utils,
    gettext,
    glib,
    gtk4,
    libadwaita,
    meson,
    ninja,
    pkg-config,
    wrapGAppsHook4,
    cairo,
    hwdata,
}:
let
    pp = python3Packages;
in
pp.buildPythonApplication {
    pname = "system-monitoring-center";
    version = "v2.26.0";

    src = fetchFromGitHub {
        # https://github.com/hakandundar34coding/system-monitoring-center/tree/4d9424833bd36cfd4fe7521139fe57faf9ee626b
        owner = "hakandundar34coding";
        repo = "system-monitoring-center";
        rev = "4d9424833bd36cfd4fe7521139fe57faf9ee626b";
        hash = "sha256-HSzQ13Y+KV8J7lzUZ4L+6NNQUIHkgF4SxPpHIKCHwt8=";
    };
    patches = [
        ./1.patch
    ];

    pyproject = false; # Build with meson

    nativeBuildInputs = [
        meson
        ninja
        pkg-config
        wrapGAppsHook4
        desktop-file-utils
        gobject-introspection
        gettext
    ];
    buildInputs = [
        glib
        gtk4
        libadwaita
        cairo
    ];
    propagatedBuildInputs = [
        pp.pygobject3
    ];

    dontWrapGApps = true;

    preFixup = ''
        makeWrapperArgs+=(--set HWDATA_PATH "${hwdata}/share/hwdata/")
        makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
    '';
}

1.patch:

commit 5d4c39d71288296f0800da409a4c1eab2b9670da
Author: borisoid <boris.lazarev90@mail.ru>
Date:   Sat Aug 24 14:22:26 2024 +0300

    Patch for NixOS

diff --git a/src/Libsysmon.py b/src/Libsysmon.py
index e2a91b0..288101c 100644
--- a/src/Libsysmon.py
+++ b/src/Libsysmon.py
@@ -5,6 +5,7 @@ import platform
 import time
 from datetime import datetime
 import threading
+import functools
 
 
 # ***********************************************************************************************
@@ -146,6 +147,12 @@ def get_environment_type():
     return environment_type
 
 
+@functools.cache
+def is_nixos() -> bool:
+    with open("/etc/os-release") as reader:
+        return "ID=nixos" in reader.read().strip().split("\n")
+
+
 def get_init_system():
     """
     Get init system of the OS. Currently it is detected as "systemd" or "other".
@@ -264,6 +271,8 @@ def get_device_vendor_model(modalias_output):
     # Define hardware database file directories.
     udev_database = "no"
     pci_usb_hardware_database_dir = "/usr/share/hwdata/"
+    if is_nixos():
+        pci_usb_hardware_database_dir = os.getenv("HWDATA_PATH", pci_usb_hardware_database_dir)
     if environment_type == "flatpak":
         pci_usb_hardware_database_dir = "/app/share/hwdata/"
     sdio_hardware_database_dir = os.path.dirname(os.path.realpath(__file__)) + "/../database/"
@@ -4083,14 +4092,15 @@ def get_application_name_image_dict():
     application_image_dict = {}
 
     # Get ".desktop" file names
-    application_file_list = [file for file in os.listdir("/usr/share/applications/") if file.endswith(".desktop")]
+    applications_dir = "/usr/share/applications/" if not is_nixos() else "/run/current-system/sw/share/applications/"
+    application_file_list = [file for file in os.listdir(applications_dir) if file.endswith(".desktop")]
 
     # Get application name and image information
     for application in application_file_list:
 
         # "encoding="utf-8"" is used for preventing "UnicodeDecodeError" errors during reading the file content if "C" locale is used.
         try:
-            with open("/usr/share/applications/" + application, encoding="utf-8") as reader:
+            with open(applications_dir + application, encoding="utf-8") as reader:
                 application_file_content = reader.read()
         except (PermissionError, FileNotFoundError) as e:
             continue
@@ -4520,6 +4530,9 @@ def get_services_information():
             for file in service_unit_file_list_lib_systemd_scratch:
                 if file.endswith(".service") == True:
                     service_unit_file_list_lib_systemd.append(file)
+    elif is_nixos():
+        service_unit_files_dir = "/run/current-system/sw/lib/systemd/system/"
+        service_unit_file_list_usr_lib_systemd = [filename for filename in os.listdir(service_unit_files_dir) if filename.endswith(".service")]
     else:
         if os.path.isdir("/usr/lib/systemd/system/") == True:
             service_unit_files_dir = "/usr/lib/systemd/system/"