From 1c5fe46083e55f912f494ca152a9e373bb77d3f4 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Mon, 7 Oct 2024 22:33:32 +0300 Subject: [PATCH 01/32] fix macos build --- CMakeLists.txt | 6 +++++- mainwindow.cpp | 2 +- qldd.cpp | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 068f61a..f868b60 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ set(QtVer Qt5) find_package(${QtVer} COMPONENTS Widgets QUIET) if (NOT Qt5_FOUND) set(QtVer Qt6) - set(QtVerPath qt6) + set(QtVerPath qt6) find_package(${QtVer} COMPONENTS Widgets REQUIRED) endif () endif() @@ -163,6 +163,10 @@ if(APPLE) OUTPUT_VARIABLE brew_path) string(STRIP ${brew_path} brew_path) + link_directories("${brew_path}/lib") + add_custom_command(TARGET Qldd + POST_BUILD + COMMAND ln -s "${brew_path}/lib" "lib") add_custom_command(TARGET Qldd POST_BUILD COMMAND "${brew_path}/opt/${QtVerPath}/bin/macdeployqt" Qldd.app) diff --git a/mainwindow.cpp b/mainwindow.cpp index 53b9610..8d92d3a 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -239,7 +239,7 @@ void MainWindow::initDemangleRules() { QJsonDocument doc = QJsonDocument::fromJson(val.toUtf8()); QJsonObject obj = doc.object(); QJsonArray arrRules = obj["rules"].toArray(); - for (const auto &item : qAsConst(arrRules)) { + for (const auto &item : arrRules) { auto itemObj = item.toObject(); auto itemKeys = itemObj.keys(); if (itemKeys.size() == 1) { diff --git a/qldd.cpp b/qldd.cpp index 79524d6..8006d00 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -92,7 +92,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { sl = line.split(DEPEND_SPLITTER); } int i = 0; - for (const QString &v : qAsConst(sl)) { + for (const QString &v : sl) { if (v.contains("(0x") || v.contains("(compatibility")) { QStringList slTmp = v.split("("); if (slTmp.size() > 1) { @@ -122,7 +122,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { sl.removeFirst(); QTreeWidgetItem *tmp = item; QColor redC("red"); - for (const QString &v : qAsConst(sl)) { + for (const QString &v : sl) { if (!v.trimmed().isEmpty()) { if (v.contains("not found")) { tmp->setForeground(0, QBrush(redC)); @@ -191,7 +191,7 @@ QString QLdd::getInfo() { execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { buf.append(line + "\n"); }); QStringList slTmp = buf.split(INFO_SPLITTER); buf.clear(); - for (const QString &v : qAsConst(slTmp)) { + for (const QString &v : slTmp) { buf.append(v.trimmed()).append("\n"); } return buf; From 4ce5c51afa8b3bb25c2f0a078cb7b51ea650fc77 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Mon, 7 Oct 2024 22:38:53 +0300 Subject: [PATCH 02/32] try to fix cmake option for qt5 --- .github/workflows/action-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 993367c..357872f 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -414,7 +414,7 @@ jobs: - name: mkdir run: mkdir cmake-build-release - name: cmake cmake-build-release - run: cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt5 -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. + run: cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt@5 -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. - name: cmake make run: cmake --build cmake-build-release/ --target all env: From 338d0ee06190b935f4559f9c6efda6c973746401 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Mon, 7 Oct 2024 22:42:48 +0300 Subject: [PATCH 03/32] fix qt5 path for brew --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f868b60..07bc3a5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,17 +56,17 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Threads REQUIRED) -set(QtVerPath qt5) +set(QtVerPath qt@5) if (FORCE_QT6) set(QtVer Qt6) - set(QtVerPath qt6) + set(QtVerPath qt@6) find_package(${QtVer} COMPONENTS Widgets REQUIRED) else() set(QtVer Qt5) find_package(${QtVer} COMPONENTS Widgets QUIET) if (NOT Qt5_FOUND) set(QtVer Qt6) - set(QtVerPath qt6) + set(QtVerPath qt@6) find_package(${QtVer} COMPONENTS Widgets REQUIRED) endif () endif() From 9af145bc4ddca1d1ed8a4f7f1c031de1fa918e7c Mon Sep 17 00:00:00 2001 From: Alexander B Date: Tue, 8 Oct 2024 10:33:13 +0300 Subject: [PATCH 04/32] fedora_latest:s390x and fedora_latest:ppc64le doesn't exist --- .github/workflows/action-cpp.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 357872f..7c53d72 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -17,10 +17,10 @@ jobs: distro: ubuntu_latest - arch: aarch64 distro: fedora_latest - - arch: ppc64le - distro: fedora_latest - - arch: s390x - distro: fedora_latest + # - arch: ppc64le + # distro: fedora_latest + # - arch: s390x + # distro: fedora_latest steps: - name: Get current date id: date @@ -85,6 +85,7 @@ jobs: matrix: container: - ubuntu:22.04 + - ubuntu:24.04 container: image: ${{ matrix.container }} From bbd4efc8193388be0abb792021ea081cc9109742 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Tue, 8 Oct 2024 10:53:10 +0300 Subject: [PATCH 05/32] disable fedira build on non x86 platforms --- .github/workflows/action-cpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 7c53d72..1815149 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -15,8 +15,8 @@ jobs: distro: ubuntu_latest - arch: riscv64 distro: ubuntu_latest - - arch: aarch64 - distro: fedora_latest + # - arch: aarch64 + # distro: fedora_latest # - arch: ppc64le # distro: fedora_latest # - arch: s390x From 37b87e16fd2ba78a3ae9cb7081af63379d6bde64 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Tue, 8 Oct 2024 11:13:20 +0300 Subject: [PATCH 06/32] update action functions versions --- .github/workflows/action-cpp.yml | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 1815149..f35c831 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -31,7 +31,7 @@ jobs: - name: Set artifacts dir id: artifacts run: echo "artifacts=$(echo $PWD/artifacts)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v2 name: Run commands id: configure-and-build @@ -70,7 +70,7 @@ jobs: id: get-version run: echo "prj_ver=$(cat ${PWD}/artifacts/VERSION.txt)" >> $GITHUB_OUTPUT - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}-{2}', steps.get-version.outputs.prj_ver, matrix.distro, matrix.arch) }} path: ${{ format('{0}/Qldd-{1}-{2}.???', steps.artifacts.outputs.artifacts ,steps.get-version.outputs.prj_ver, matrix.arch) }} @@ -97,7 +97,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release @@ -116,7 +116,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -124,7 +124,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -149,7 +149,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: | @@ -170,7 +170,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -178,7 +178,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -203,7 +203,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release @@ -222,7 +222,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -230,7 +230,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -255,7 +255,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: apt-get update -y && apt-get install -yq binutils python-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release @@ -274,7 +274,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -282,7 +282,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -307,7 +307,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: | @@ -332,7 +332,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -340,7 +340,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -365,7 +365,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb @@ -384,7 +384,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -392,7 +392,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -407,7 +407,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: brew-pkgs run: brew install qt@5 @@ -425,7 +425,7 @@ jobs: run: echo "prj_ver=$(cat ./VERSION.txt)" >> $GITHUB_OUTPUT - name: cpack run: cd cmake-build-release && cpack -G DragNDrop && cd .. - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}-Darwin.dmg', steps.get-version.outputs.prj_ver) }} path: cmake-build-release/${{ format('Qldd-{0}-Darwin.dmg', steps.get-version.outputs.prj_ver) }} From d9348da22afc9780afaa46cdb17f76af82712c28 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:26:06 +0300 Subject: [PATCH 07/32] add CLAUDE.md and performance/correctness task plan Documents build commands, architecture, and code style in CLAUDE.md. Adds 9 task files under tasks/ covering threading safety, debug logging, widget update batching, and other performance/correctness improvements. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 87 +++++++++++++++++++ tasks/task-01-fillexporttable-threading.md | 21 +++++ tasks/task-02-remove-debug-logging.md | 32 +++++++ tasks/task-03-demangle-heap-alloc.md | 28 ++++++ tasks/task-04-widget-update-batching.md | 34 ++++++++ tasks/task-05-qcolor-outside-loop.md | 29 +++++++ tasks/task-06-cache-dynamic-cast.md | 34 ++++++++ tasks/task-07-getinfo-double-build.md | 42 +++++++++ tasks/task-08-copy-export-item-bug.md | 52 +++++++++++ tasks/task-09-qdir-setcurrent-global-state.md | 37 ++++++++ 10 files changed, 396 insertions(+) create mode 100644 CLAUDE.md create mode 100644 tasks/task-01-fillexporttable-threading.md create mode 100644 tasks/task-02-remove-debug-logging.md create mode 100644 tasks/task-03-demangle-heap-alloc.md create mode 100644 tasks/task-04-widget-update-batching.md create mode 100644 tasks/task-05-qcolor-outside-loop.md create mode 100644 tasks/task-06-cache-dynamic-cast.md create mode 100644 tasks/task-07-getinfo-double-build.md create mode 100644 tasks/task-08-copy-export-item-bug.md create mode 100644 tasks/task-09-qdir-setcurrent-global-state.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9dffa51 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project overview + +**Qldd** (DependencyViewer) is a Qt-based GUI application that shows all dependent libraries of a given executable or shared library on GNU/Linux and macOS. It wraps `ldd` (Linux) / `otool` (macOS), `file`, and `nm` shell commands and presents results in a tree/list view. There are no automated tests — the app is verified manually. + +## Build + +```bash +# Configure (from repo root) +cmake -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. +# Or for a debug build: +cmake -DCMAKE_BUILD_TYPE=Debug -Bcmake-build-debug -H. + +# Build +cmake --build cmake-build-release/ --target all + +# macOS: Qt5 must be installed via Homebrew and its prefix passed explicitly +cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt@5 -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. +``` + +CMake auto-detects Qt5 first; falls back to Qt6. To force Qt6: +```bash +cmake -DFORCE_QT6=ON ... +``` + +### Packaging + +```bash +cd cmake-build-release +cpack -G DEB # Ubuntu/Debian → .deb +cpack -G RPM # Fedora/CentOS → .rpm +cpack -G DragNDrop # macOS → .dmg +``` + +## Architecture + +The app is a single-window Qt application with no separate library targets. + +| File | Role | +|---|---| +| `main.cpp` | Entry point — parses optional filename arg, constructs `MainWindow` | +| `mainwindow.{cpp,h,ui}` | Top-level Qt window; owns a `QLdd` instance, manages menus, file open dialog, export-filter UI, demangling rules, and permission checkboxes | +| `qldd.{cpp,h}` | Core logic — runs `ldd`/`otool`, `file`, `nm` via `popen`; parses output into tree nodes for `QTreeWidget` and list entries for `QListWidget`; reads file metadata (size, timestamps, owner/group, permissions) | +| `demanglerules.{cpp,h,ui}` | Dialog + data model for configuring regex-based symbol demangling rules; rules persist to/from a JSON file | +| `finfdialog.{cpp,h,ui}` | "File info" dialog that displays metadata gathered by `QLdd` | +| `customtypes.h` | Shared typedefs (`RulesMap`) used across the codebase | +| `resources/rules.json` | Default demangling rules shipped with the app | +| `config.h.in` / `version.h.in` | CMake-configured headers providing `CONFIG_PATH` and build version info | +| `cmake/modules/` | `FindVersionHeader.cmake` — extracts git SHA/date into `version.h`; `FindConfigPath.cmake` — resolves user config directory | + +### Key design pattern + +`execAndDoOnEveryLine` (defined in `qldd.h`) is a generic template that runs an arbitrary shell command via `popen` and calls an action callback for each output line, optionally async. All `ldd`/`otool`/`nm`/`file` invocations go through this helper. + +### File-manager integrations + +- `nautilus-python/extensions/` — Python extension for Nautilus (GNOME) +- `nemo/actions/` — Nemo action file +- `dolphin/` — KDE service menu entry + +These are installed as separate CPack components (`nautilus-integration`, `dolphin-integration`) and are not part of the C++ build. + +## Code style + +`.clang-format` and `.clang-tidy` are present. Run clang-format before committing: +```bash +clang-format -i *.cpp *.h +``` + +## Pending performance / correctness tasks + +Detailed task files live in `tasks/`. Do not implement fixes not listed here without first checking whether an existing task covers the change. + +| ID | File(s) | Priority | Status | Summary | +|---|---|---|---|---| +| [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | TODO | Qt widget calls from worker threads — undefined behavior / crash risk | +| [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | TODO | `qDebug` in hot symbol loop fires thousands of times in release builds | +| [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | TODO | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | +| [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | TODO | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | +| [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | TODO | `QColor("red")` constructed per dependency line; replace with `Qt::red` | +| [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | TODO | `dynamic_cast` repeated per row in loop; cache the result | +| [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | TODO | `getInfo` builds string twice; eliminate intermediate `QStringList` | +| [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | TODO | `copyExportItem` always copies `currentRow()` regardless of selection | +| [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | TODO | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/tasks/task-01-fillexporttable-threading.md b/tasks/task-01-fillexporttable-threading.md new file mode 100644 index 0000000..94e42bf --- /dev/null +++ b/tasks/task-01-fillexporttable-threading.md @@ -0,0 +1,21 @@ +# Task 01 — Fix thread-safety bug in fillExportTable + +**Priority:** Critical +**Status:** TODO +**File:** `qldd.cpp:145–177` + +## Problem + +`fillExportTable` uses `Exec::ASYNC` which calls `std::async(std::launch::async, ...)` for every line of `nm` output. This can spawn hundreds or thousands of threads for a large binary. More critically, `QListWidget::addItem` is called from worker threads — Qt widgets must only be touched from the main thread. The mutex around `addItem` does not make it safe; this is undefined behavior and a latent crash. + +## Fix + +1. Switch the lambda to collect results into a `QVector` (protected by the mutex, or using a lock-free append into a pre-allocated vector). +2. After all async work completes (futures resolved), post the collected items back to the main thread and populate the widget there — or use `QMetaObject::invokeMethod(..., Qt::QueuedConnection)` from inside the lambda to queue each `addItem` call to the main thread. +3. Alternatively, drop `Exec::ASYNC` here entirely and run symbol parsing on a `QThread` / `QtConcurrent::run`, emitting a signal when done. + +## Acceptance criteria + +- No Qt widget calls from worker threads. +- UI remains responsive during `nm` parsing for large binaries. +- `nm` output is still processed in parallel (demangling + regex replacements are CPU-bound and safe to parallelize). diff --git a/tasks/task-02-remove-debug-logging.md b/tasks/task-02-remove-debug-logging.md new file mode 100644 index 0000000..ae6a4c4 --- /dev/null +++ b/tasks/task-02-remove-debug-logging.md @@ -0,0 +1,32 @@ +# Task 02 — Remove debug logging from hot symbol-processing loop + +**Priority:** High +**Status:** TODO +**File:** `qldd.cpp:162–164` + +## Problem + +```cpp +if (demangled.contains("string")) { + qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; +} +``` + +This fires inside the inner demangling loop for every rule applied to every exported symbol that contains `"string"`. For a typical C++ binary this means thousands of `qDebug()` calls in release builds. `qDebug()` formats and writes to stderr unconditionally unless `QT_NO_DEBUG_OUTPUT` is defined. + +## Fix + +Remove the three lines entirely. They appear to be leftover debugging from development. If retained for development purposes, guard with: + +```cpp +#ifdef QT_DEBUG +if (demangled.contains("string")) { + qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; +} +#endif +``` + +## Acceptance criteria + +- No `qDebug` output in release builds from this loop. +- Lines removed or wrapped in `#ifdef QT_DEBUG`. diff --git a/tasks/task-03-demangle-heap-alloc.md b/tasks/task-03-demangle-heap-alloc.md new file mode 100644 index 0000000..1a998e6 --- /dev/null +++ b/tasks/task-03-demangle-heap-alloc.md @@ -0,0 +1,28 @@ +# Task 03 — Eliminate redundant heap allocation in __cxa_demangle call + +**Priority:** Medium +**Status:** TODO +**File:** `qldd.cpp:156` + +## Problem + +```cpp +char *realname = abi::__cxa_demangle(info.at(2).toStdString().c_str(), nullptr, nullptr, &status); +``` + +`toStdString()` allocates a `std::string` on the heap just to call `.c_str()` on it. This happens for every exported symbol. + +## Fix + +Replace with `toUtf8().constData()` which returns a pointer into Qt's internal copy-on-write buffer without a heap allocation: + +```cpp +char *realname = abi::__cxa_demangle(info.at(2).toUtf8().constData(), nullptr, nullptr, &status); +``` + +Note: the `QByteArray` returned by `toUtf8()` must stay alive for the duration of the `constData()` pointer use. Since `__cxa_demangle` returns before the temporary is destroyed, this is safe as a function argument. + +## Acceptance criteria + +- `toStdString()` call removed at `qldd.cpp:156`. +- No intermediate `std::string` heap allocation per symbol. diff --git a/tasks/task-04-widget-update-batching.md b/tasks/task-04-widget-update-batching.md new file mode 100644 index 0000000..e623c91 --- /dev/null +++ b/tasks/task-04-widget-update-batching.md @@ -0,0 +1,34 @@ +# Task 04 — Batch widget updates in fillDependency and fillExportTable + +**Priority:** High +**Status:** TODO +**Files:** `qldd.cpp:77–143`, `qldd.cpp:145–177` + +## Problem + +`fillDependency` calls `treeWidget.addTopLevelItem()` for every dependency line, and `fillExportTable` calls `listWidget.addItem()` for every exported symbol. Each call triggers an incremental layout recalculation and repaint. For binaries with many dependencies or hundreds of exports the UI visibly redraws item-by-item and the calls collectively dominate render time. + +## Fix + +Wrap the population loops with `setUpdatesEnabled`: + +```cpp +// fillDependency +treeWidget.setUpdatesEnabled(false); +// ... all addTopLevelItem / child item calls ... +treeWidget.setUpdatesEnabled(true); +``` + +```cpp +// fillExportTable +listWidget.setUpdatesEnabled(false); +// ... all addItem calls ... +listWidget.setUpdatesEnabled(true); +``` + +Ensure `setUpdatesEnabled(true)` is called even if an exception or early return occurs (use RAII guard or explicit cleanup paths). + +## Acceptance criteria + +- `setUpdatesEnabled(false/true)` wraps population in both methods. +- Filling a large binary's exports/deps appears instant rather than incrementally painted. diff --git a/tasks/task-05-qcolor-outside-loop.md b/tasks/task-05-qcolor-outside-loop.md new file mode 100644 index 0000000..746b5bf --- /dev/null +++ b/tasks/task-05-qcolor-outside-loop.md @@ -0,0 +1,29 @@ +# Task 05 — Move QColor construction outside per-line lambda + +**Priority:** Low +**Status:** TODO +**File:** `qldd.cpp:124` + +## Problem + +```cpp +// inside the per-dependency-line lambda: +QColor redC("red"); +``` + +`QColor("red")` parses the color name string on every invocation of the lambda (i.e., once per line of `ldd`/`otool` output). While cheap individually, it is unnecessary repeated work. + +## Fix + +Replace with the pre-defined Qt constant: + +```cpp +tmp->setForeground(0, QBrush(Qt::red)); +``` + +This removes the string parsing entirely. The `QBrush(Qt::red)` constructor uses a compile-time constant. + +## Acceptance criteria + +- `QColor("red")` removed from the lambda body. +- `Qt::red` used directly in the `setForeground` call. diff --git a/tasks/task-06-cache-dynamic-cast.md b/tasks/task-06-cache-dynamic-cast.md new file mode 100644 index 0000000..25007b1 --- /dev/null +++ b/tasks/task-06-cache-dynamic-cast.md @@ -0,0 +1,34 @@ +# Task 06 — Cache dynamic_cast result in demanglerules::insertNewRow + +**Priority:** Low +**Status:** TODO +**File:** `demanglerules.cpp:42` + +## Problem + +```cpp +void demanglerules::insertNewRow(int row, const QString &src, const QString &dst) { + ui->tableWidget->insertRow(row); + auto *m = dynamic_cast(parent()); // repeated on every row + ... +} +``` + +`insertNewRow` is called in a loop during construction (once per rule). `dynamic_cast` walks the vtable/RTTI chain on each call. The result is always the same object. + +## Fix + +Option A — cache in the constructor and pass as a parameter: + +```cpp +void demanglerules::insertNewRow(int row, const QString &src, const QString &dst, MainWindow *m) { ... } +``` + +Option B — store `MainWindow *m` as a member in the constructor and use it in `insertNewRow` without recasting. + +The constructor already does the cast once (`demanglerules.cpp:16`), so reuse that result. + +## Acceptance criteria + +- `dynamic_cast(parent())` not called inside `insertNewRow`. +- Cached pointer used instead. diff --git a/tasks/task-07-getinfo-double-build.md b/tasks/task-07-getinfo-double-build.md new file mode 100644 index 0000000..a2b8a21 --- /dev/null +++ b/tasks/task-07-getinfo-double-build.md @@ -0,0 +1,42 @@ +# Task 07 — Eliminate double string build in getInfo + +**Priority:** Low +**Status:** TODO +**File:** `qldd.cpp:187–198` + +## Problem + +```cpp +QString buf; +execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { buf.append(line + "\n"); }); +QStringList slTmp = buf.split(INFO_SPLITTER); +buf.clear(); +for (const QString &v : slTmp) { + buf.append(v.trimmed()).append("\n"); +} +return buf; +``` + +The full output is first assembled into `buf`, then split into a `QStringList`, then reassembled. This is two full string allocations and two passes over the data. Since the lambda is called per line, trimming can happen there directly. + +## Fix + +```cpp +QString buf; +execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { + // split by INFO_SPLITTER within each line and trim inline + const auto parts = line.split(INFO_SPLITTER); + for (const auto &part : parts) { + buf.append(part.trimmed()).append("\n"); + } +}); +return buf; +``` + +This eliminates the intermediate `QStringList` and the second pass. + +## Acceptance criteria + +- `buf.split(INFO_SPLITTER)` removed. +- Single-pass construction of the final string inside the lambda. +- Output is functionally identical (each comma/newline-separated field on its own trimmed line). diff --git a/tasks/task-08-copy-export-item-bug.md b/tasks/task-08-copy-export-item-bug.md new file mode 100644 index 0000000..aa5c1e1 --- /dev/null +++ b/tasks/task-08-copy-export-item-bug.md @@ -0,0 +1,52 @@ +# Task 08 — Fix copyExportItem always copying currentRow + +**Priority:** Medium +**Status:** TODO +**File:** `mainwindow.cpp:263–272` + +## Problem + +```cpp +void MainWindow::copyExportItem() { + QClipboard *clipboard = qApp->clipboard(); + for (int i = 0; i < ui->listWidgetExportTable->selectedItems().size(); ++i) { + QListWidgetItem *item = ui->listWidgetExportTable->item(ui->listWidgetExportTable->currentRow()); + clipboard->setText(item->text()); + } +} +``` + +Two bugs: +1. The loop indexes with `i` but always fetches `currentRow()` — for multi-selection it overwrites the clipboard N times with the same item. +2. `selectedItems()` is called once per iteration (implicit in the loop condition re-evaluation). + +## Fix + +```cpp +void MainWindow::copyExportItem() { + const auto selected = ui->listWidgetExportTable->selectedItems(); + if (!selected.isEmpty()) { + qApp->clipboard()->setText(selected.last()->text()); + } +} +``` + +If multi-item copy (newline-joined) is desired: + +```cpp +void MainWindow::copyExportItem() { + QStringList texts; + for (const auto *item : ui->listWidgetExportTable->selectedItems()) { + texts << item->text(); + } + if (!texts.isEmpty()) { + qApp->clipboard()->setText(texts.join('\n')); + } +} +``` + +## Acceptance criteria + +- `currentRow()` removed from `copyExportItem`. +- Selected items iterated correctly. +- No redundant `selectedItems()` calls inside the loop condition. diff --git a/tasks/task-09-qdir-setcurrent-global-state.md b/tasks/task-09-qdir-setcurrent-global-state.md new file mode 100644 index 0000000..7440612 --- /dev/null +++ b/tasks/task-09-qdir-setcurrent-global-state.md @@ -0,0 +1,37 @@ +# Task 09 — Remove QDir::setCurrent global state from fillDependency + +**Priority:** Medium +**Status:** TODO +**File:** `qldd.cpp:82, 142` + +## Problem + +```cpp +QDir::setCurrent(getPathOfBinary()); // line 82 +// ... run ldd ... +QDir::setCurrent(_lddDirPath); // line 142 +``` + +`QDir::setCurrent` changes the working directory of the entire process. This is: +- Not thread-safe: if two `QLdd` instances ever run concurrently, the CWD races. +- Fragile: an early return or exception between lines 82 and 142 leaves the process in a wrong directory. +- Unnecessary: `ldd`/`otool` accept absolute paths. + +## Fix + +Pass the absolute path directly to the command and remove both `QDir::setCurrent` calls: + +```cpp +// Already using _fileName which is absolute — no CWD change needed. +ss << CMD_LDD << " \"" << _fileName.toStdString() << "\""; +``` + +Verify `_fileName` is always stored as an absolute path (it comes from `QFileInfo::absoluteFilePath()` or the dialog, both of which provide absolute paths). + +If there is a scenario where a relative path is passed to the constructor, normalize it to absolute in the constructor using `QFileInfo(_fileName).absoluteFilePath()`. + +## Acceptance criteria + +- Both `QDir::setCurrent` calls removed from `fillDependency`. +- `_fileName` guaranteed absolute (assert or normalize in constructor). +- No process-global CWD mutation. From 1ae85f7775b3aceb17d8d61424a64e040f66275b Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:28:16 +0300 Subject: [PATCH 08/32] fix thread-safety bug in fillExportTable (task-01) Worker threads now only perform CPU-bound work (demangling, rule application, filter check) and collect results into a std::vector. The QListWidget is populated exclusively on the main thread after all async work completes. Also adds setUpdatesEnabled batching, removes the toStdString() heap allocation, and guards against lines with fewer than 3 tokens. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- qldd.cpp | 43 ++++++++++++++-------- tasks/task-01-fillexporttable-threading.md | 2 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9dffa51..2e59131 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,7 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | ID | File(s) | Priority | Status | Summary | |---|---|---|---|---| -| [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | TODO | Qt widget calls from worker threads — undefined behavior / crash risk | +| [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | DONE | Qt widget calls from worker threads — undefined behavior / crash risk | | [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | TODO | `qDebug` in hot symbol loop fires thousands of times in release builds | | [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | TODO | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | | [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | TODO | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | diff --git a/qldd.cpp b/qldd.cpp index 8006d00..a25d478 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -147,33 +149,44 @@ void QLdd::fillExportTable(QListWidget &listWidget, const QString &filter) { std::mutex mutex; std::stringstream ss; ss << NM << " \"" << _fileName.toStdString() << "\" | grep \\ T\\ "; + + // Collect demangled results from worker threads; never touch the widget from them. + std::vector> results; + execAndDoOnEveryLine( ss.str(), - [&mutex, &listWidget, &filter, this](const QString &line) { - int status = 0; + [&mutex, &results, &filter, this](const QString &line) { QStringList info = line.split(" "); + if (info.size() < 3) return; + + int status = 0; QString demangled(info.at(2)); - char *realname = abi::__cxa_demangle(info.at(2).toStdString().c_str(), nullptr, nullptr, &status); + char *realname = abi::__cxa_demangle(info.at(2).toUtf8().constData(), nullptr, nullptr, &status); if (realname) { demangled = QString::fromLocal8Bit(realname); ::free(realname); - for (auto &_demangleRule : _demangleRules) { - demangled.replace(_demangleRule.first, _demangleRule.second); - if (demangled.contains("string")) { - qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; - } + for (const auto &rule : _demangleRules) { + demangled.replace(rule.first, rule.second); } } - std::unique_ptr item(new QListWidgetItem(info.at(0) + " " + demangled)); - item->setToolTip(demangled); - std::unique_lock lock(mutex); - if (!filter.isEmpty() && demangled.contains(filter, Qt::CaseInsensitive)) { - listWidget.addItem(item.release()); - } else if (filter.isEmpty()) { - listWidget.addItem(item.release()); + + if (!filter.isEmpty() && !demangled.contains(filter, Qt::CaseInsensitive)) { + return; } + + std::unique_lock lock(mutex); + results.emplace_back(info.at(0), std::move(demangled)); }, Exec::ASYNC); + + // All async work is done — populate the widget on the main thread. + listWidget.setUpdatesEnabled(false); + for (const auto &r : results) { + auto *item = new QListWidgetItem(r.first + " " + r.second); + item->setToolTip(r.second); + listWidget.addItem(item); + } + listWidget.setUpdatesEnabled(true); } QString QLdd::getPathOfBinary() { return _fileInfo.absolutePath(); } diff --git a/tasks/task-01-fillexporttable-threading.md b/tasks/task-01-fillexporttable-threading.md index 94e42bf..b307a16 100644 --- a/tasks/task-01-fillexporttable-threading.md +++ b/tasks/task-01-fillexporttable-threading.md @@ -1,7 +1,7 @@ # Task 01 — Fix thread-safety bug in fillExportTable **Priority:** Critical -**Status:** TODO +**Status:** DONE **File:** `qldd.cpp:145–177` ## Problem From 64952e9065a61b1b6bc67715770e5ec5040caeff Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:31:19 +0300 Subject: [PATCH 09/32] fix ln -s failing on incremental builds on macOS Use ln -sf so the lib symlink is replaced rather than erroring when it already exists from a previous build. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07bc3a5..9a40b36 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,7 @@ if(APPLE) link_directories("${brew_path}/lib") add_custom_command(TARGET Qldd POST_BUILD - COMMAND ln -s "${brew_path}/lib" "lib") + COMMAND ln -sf "${brew_path}/lib" "lib") add_custom_command(TARGET Qldd POST_BUILD COMMAND "${brew_path}/opt/${QtVerPath}/bin/macdeployqt" Qldd.app) From bc56300267f606a5374466d8690bfb50c73efc79 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:36:58 +0300 Subject: [PATCH 10/32] re-sign app bundle after macdeployqt to fix launch crash on macOS 15+ macdeployqt copies Homebrew dylibs (libjpeg, libtiff, etc.) into the bundle without signing them. macOS 15+ kills the process at launch with CODESIGNING / Invalid Page when dyld tries to map an unsigned dylib page. Adding a codesign --force --deep --sign - step after macdeployqt applies an ad-hoc signature to the entire bundle, satisfying the OS requirement. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a40b36..7920626 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,11 @@ if(APPLE) add_custom_command(TARGET Qldd POST_BUILD COMMAND "${brew_path}/opt/${QtVerPath}/bin/macdeployqt" Qldd.app) + # Re-sign the entire bundle after macdeployqt copies unsigned Homebrew dylibs into it. + # Without this macOS 15+ kills the process with CODESIGNING / Invalid Page on launch. + add_custom_command(TARGET Qldd + POST_BUILD + COMMAND codesign --force --deep --sign - Qldd.app) else() # Components: From 5c7e1322fd3da662a1d671e9ea20f3dd0d579700 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:38:30 +0300 Subject: [PATCH 11/32] mark task-02 done (resolved by task-01 rewrite) The qDebug block was removed when the fillExportTable lambda was rewritten as part of the task-01 threading fix. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- tasks/task-02-remove-debug-logging.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2e59131..7634078 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,7 +77,7 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | ID | File(s) | Priority | Status | Summary | |---|---|---|---|---| | [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | DONE | Qt widget calls from worker threads — undefined behavior / crash risk | -| [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | TODO | `qDebug` in hot symbol loop fires thousands of times in release builds | +| [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | DONE | `qDebug` in hot symbol loop fires thousands of times in release builds | | [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | TODO | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | | [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | TODO | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | | [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | TODO | `QColor("red")` constructed per dependency line; replace with `Qt::red` | diff --git a/tasks/task-02-remove-debug-logging.md b/tasks/task-02-remove-debug-logging.md index ae6a4c4..0821287 100644 --- a/tasks/task-02-remove-debug-logging.md +++ b/tasks/task-02-remove-debug-logging.md @@ -1,7 +1,7 @@ # Task 02 — Remove debug logging from hot symbol-processing loop **Priority:** High -**Status:** TODO +**Status:** DONE (resolved as part of task-01 rewrite) **File:** `qldd.cpp:162–164` ## Problem From 3a921fcc0429270e792b5c4f094b1482bc0b55ca Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:39:07 +0300 Subject: [PATCH 12/32] mark task-03 done (resolved by task-01 rewrite) toUtf8().constData() was already used in the rewritten fillExportTable lambda, replacing the toStdString().c_str() heap allocation. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- tasks/task-03-demangle-heap-alloc.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7634078..7e9c94c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,7 +78,7 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit |---|---|---|---|---| | [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | DONE | Qt widget calls from worker threads — undefined behavior / crash risk | | [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | DONE | `qDebug` in hot symbol loop fires thousands of times in release builds | -| [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | TODO | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | +| [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | DONE | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | | [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | TODO | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | | [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | TODO | `QColor("red")` constructed per dependency line; replace with `Qt::red` | | [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | TODO | `dynamic_cast` repeated per row in loop; cache the result | diff --git a/tasks/task-03-demangle-heap-alloc.md b/tasks/task-03-demangle-heap-alloc.md index 1a998e6..c656f3f 100644 --- a/tasks/task-03-demangle-heap-alloc.md +++ b/tasks/task-03-demangle-heap-alloc.md @@ -1,7 +1,7 @@ # Task 03 — Eliminate redundant heap allocation in __cxa_demangle call **Priority:** Medium -**Status:** TODO +**Status:** DONE (resolved as part of task-01 rewrite) **File:** `qldd.cpp:156` ## Problem From 2af24a154f2ac830ce4724e2612b30c912b1bad9 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:40:42 +0300 Subject: [PATCH 13/32] batch widget updates in fillDependency, replace QColor("red") (task-04, task-05) Wrap the treeWidget population loop with setUpdatesEnabled(false/true) to eliminate per-item repaints when loading large binaries. Also replace QColor("red") constructed per line with the Qt::red compile-time constant. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 4 ++-- qldd.cpp | 5 +++-- tasks/task-04-widget-update-batching.md | 2 +- tasks/task-05-qcolor-outside-loop.md | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7e9c94c..74e860c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,8 +79,8 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | DONE | Qt widget calls from worker threads — undefined behavior / crash risk | | [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | DONE | `qDebug` in hot symbol loop fires thousands of times in release builds | | [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | DONE | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | -| [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | TODO | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | -| [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | TODO | `QColor("red")` constructed per dependency line; replace with `Qt::red` | +| [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | DONE | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | +| [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | DONE | `QColor("red")` constructed per dependency line; replace with `Qt::red` | | [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | TODO | `dynamic_cast` repeated per row in loop; cache the result | | [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | TODO | `getInfo` builds string twice; eliminate intermediate `QStringList` | | [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | TODO | `copyExportItem` always copies `currentRow()` regardless of selection | diff --git a/qldd.cpp b/qldd.cpp index a25d478..5aa5e9b 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -84,6 +84,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { QDir::setCurrent(getPathOfBinary()); ss << CMD_LDD << " \"" << _fileName.toStdString() << "\""; + treeWidget.setUpdatesEnabled(false); execAndDoOnEveryLine(ss.str(), [this, &treeWidget](const QString &line) { QTreeWidgetItem *item = nullptr; QStringList sl; @@ -123,11 +124,10 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { treeWidget.addTopLevelItem(item); sl.removeFirst(); QTreeWidgetItem *tmp = item; - QColor redC("red"); for (const QString &v : sl) { if (!v.trimmed().isEmpty()) { if (v.contains("not found")) { - tmp->setForeground(0, QBrush(redC)); + tmp->setForeground(0, QBrush(Qt::red)); tmp->setText(0, tmp->text(0) + " " + v); tmp->setToolTip(0, tmp->text(0)); } else { @@ -140,6 +140,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { } } }); + treeWidget.setUpdatesEnabled(true); QDir::setCurrent(_lddDirPath); } diff --git a/tasks/task-04-widget-update-batching.md b/tasks/task-04-widget-update-batching.md index e623c91..9102f0b 100644 --- a/tasks/task-04-widget-update-batching.md +++ b/tasks/task-04-widget-update-batching.md @@ -1,7 +1,7 @@ # Task 04 — Batch widget updates in fillDependency and fillExportTable **Priority:** High -**Status:** TODO +**Status:** DONE **Files:** `qldd.cpp:77–143`, `qldd.cpp:145–177` ## Problem diff --git a/tasks/task-05-qcolor-outside-loop.md b/tasks/task-05-qcolor-outside-loop.md index 746b5bf..e581d60 100644 --- a/tasks/task-05-qcolor-outside-loop.md +++ b/tasks/task-05-qcolor-outside-loop.md @@ -1,7 +1,7 @@ # Task 05 — Move QColor construction outside per-line lambda **Priority:** Low -**Status:** TODO +**Status:** DONE (resolved together with task-04) **File:** `qldd.cpp:124` ## Problem From 520ab4254479991122702ef3b1d2e9037e3bf39f Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:42:14 +0300 Subject: [PATCH 14/32] cache dynamic_cast result in demanglerules (task-06) Store MainWindow* as a member _mainWindow, set once in the constructor from the dynamic_cast already performed there. Replace all subsequent dynamic_cast(parent()) calls in insertNewRow and on_buttonBox_accepted with the cached pointer. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- demanglerules.cpp | 8 ++++---- demanglerules.h | 3 +++ tasks/task-06-cache-dynamic-cast.md | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 74e860c..f1fc015 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,7 +81,7 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | DONE | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | | [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | DONE | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | | [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | DONE | `QColor("red")` constructed per dependency line; replace with `Qt::red` | -| [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | TODO | `dynamic_cast` repeated per row in loop; cache the result | +| [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | DONE | `dynamic_cast` repeated per row in loop; cache the result | | [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | TODO | `getInfo` builds string twice; eliminate intermediate `QStringList` | | [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | TODO | `copyExportItem` always copies `currentRow()` regardless of selection | | [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | TODO | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/demanglerules.cpp b/demanglerules.cpp index e542fd3..e11b229 100644 --- a/demanglerules.cpp +++ b/demanglerules.cpp @@ -11,9 +11,9 @@ static T enum_cast(demanglerules::Fields field) { return static_cast(field); }; -demanglerules::demanglerules(QWidget *parent) : QDialog(parent), ui(new Ui::demanglerules) { +demanglerules::demanglerules(QWidget *parent) : QDialog(parent), ui(new Ui::demanglerules), _mainWindow(dynamic_cast(parent)) { ui->setupUi(this); - auto *m = dynamic_cast(parent); + auto *m = _mainWindow; ui->tableWidget->setColumnCount(enum_cast(Fields::COUNT)); const auto &rulesRef = m->demangleRules(); QStringList tableHeader; @@ -39,7 +39,7 @@ demanglerules::~demanglerules() { delete ui; } void demanglerules::insertNewRow(int row, const QString &src, const QString &dst) { ui->tableWidget->insertRow(row); - auto *m = dynamic_cast(parent()); + auto *m = _mainWindow; auto *checkBoxWidget = new QWidget(); auto *checkBox = new QCheckBox(); @@ -96,7 +96,7 @@ void demanglerules::on_pBRemoveRule_clicked() { } void demanglerules::on_buttonBox_accepted() { - auto *m = dynamic_cast(parent()); + auto *m = _mainWindow; RulesMap rules; for (int i = 0; i < ui->tableWidget->rowCount(); ++i) { auto *itemSrc = ui->tableWidget->item(i, enum_cast(Fields::Source)); diff --git a/demanglerules.h b/demanglerules.h index bcef12e..611d1ac 100644 --- a/demanglerules.h +++ b/demanglerules.h @@ -3,6 +3,8 @@ #include +class MainWindow; + namespace Ui { class demanglerules; } @@ -17,6 +19,7 @@ class demanglerules : public QDialog { private: Ui::demanglerules *ui; + MainWindow *_mainWindow; void insertNewRow(int row, const QString &src, const QString &dst); private slots: void selectRow(bool flag); diff --git a/tasks/task-06-cache-dynamic-cast.md b/tasks/task-06-cache-dynamic-cast.md index 25007b1..018f2d9 100644 --- a/tasks/task-06-cache-dynamic-cast.md +++ b/tasks/task-06-cache-dynamic-cast.md @@ -1,7 +1,7 @@ # Task 06 — Cache dynamic_cast result in demanglerules::insertNewRow **Priority:** Low -**Status:** TODO +**Status:** DONE **File:** `demanglerules.cpp:42` ## Problem From e30498b6d41a724bdfafc2382a5e22f8dcef4d6c Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:43:07 +0300 Subject: [PATCH 15/32] eliminate double string build in getInfo (task-07) Split and trim each line inline inside the execAndDoOnEveryLine callback, removing the intermediate QStringList allocation and the second pass over the data. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- qldd.cpp | 11 +++++------ tasks/task-07-getinfo-double-build.md | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f1fc015..590216d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,6 +82,6 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | DONE | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | | [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | DONE | `QColor("red")` constructed per dependency line; replace with `Qt::red` | | [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | DONE | `dynamic_cast` repeated per row in loop; cache the result | -| [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | TODO | `getInfo` builds string twice; eliminate intermediate `QStringList` | +| [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | DONE | `getInfo` builds string twice; eliminate intermediate `QStringList` | | [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | TODO | `copyExportItem` always copies `currentRow()` regardless of selection | | [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | TODO | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/qldd.cpp b/qldd.cpp index 5aa5e9b..9c08c15 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -202,12 +202,11 @@ QString QLdd::getInfo() { std::stringstream ss; ss << "file \"" << _fileName.toStdString() << "\""; QString buf; - execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { buf.append(line + "\n"); }); - QStringList slTmp = buf.split(INFO_SPLITTER); - buf.clear(); - for (const QString &v : slTmp) { - buf.append(v.trimmed()).append("\n"); - } + execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { + for (const QString &part : line.split(INFO_SPLITTER)) { + buf.append(part.trimmed()).append("\n"); + } + }); return buf; } const QMOD &QLdd::getOwnerMod() const { return _ownerMod; } diff --git a/tasks/task-07-getinfo-double-build.md b/tasks/task-07-getinfo-double-build.md index a2b8a21..ea3e29e 100644 --- a/tasks/task-07-getinfo-double-build.md +++ b/tasks/task-07-getinfo-double-build.md @@ -1,7 +1,7 @@ # Task 07 — Eliminate double string build in getInfo **Priority:** Low -**Status:** TODO +**Status:** DONE **File:** `qldd.cpp:187–198` ## Problem From aa8c91be3e315590d21c9a44955967c963aea843 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:44:09 +0300 Subject: [PATCH 16/32] fix copyExportItem always copying currentRow (task-08) Iterate selectedItems() directly instead of looping by index and fetching currentRow() each time. Multiple selected items are joined with newlines so the clipboard gets all of them in one copy. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- mainwindow.cpp | 13 ++++++------- tasks/task-08-copy-export-item-bug.md | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 590216d..0d3c721 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,5 +83,5 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | DONE | `QColor("red")` constructed per dependency line; replace with `Qt::red` | | [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | DONE | `dynamic_cast` repeated per row in loop; cache the result | | [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | DONE | `getInfo` builds string twice; eliminate intermediate `QStringList` | -| [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | TODO | `copyExportItem` always copies `currentRow()` regardless of selection | +| [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | DONE | `copyExportItem` always copies `currentRow()` regardless of selection | | [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | TODO | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/mainwindow.cpp b/mainwindow.cpp index 8d92d3a..38b604d 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -261,13 +261,12 @@ void MainWindow::showContextMenu(const QPoint &pos) { } void MainWindow::copyExportItem() { - QClipboard *clipboard = qApp->clipboard(); - // If multiple selection is on, we need to erase all selected items - for (int i = 0; i < ui->listWidgetExportTable->selectedItems().size(); ++i) { - // Get curent item on selected row - QListWidgetItem *item = ui->listWidgetExportTable->item(ui->listWidgetExportTable->currentRow()); - // And copy text from it - clipboard->setText(item->text()); + QStringList texts; + for (const auto *item : ui->listWidgetExportTable->selectedItems()) { + texts << item->text(); + } + if (!texts.isEmpty()) { + qApp->clipboard()->setText(texts.join('\n')); } } void MainWindow::on_checkBoxOwnerRead_clicked(bool checked) { ui->checkBoxOwnerRead->setChecked(!checked); } diff --git a/tasks/task-08-copy-export-item-bug.md b/tasks/task-08-copy-export-item-bug.md index aa5c1e1..619a8e1 100644 --- a/tasks/task-08-copy-export-item-bug.md +++ b/tasks/task-08-copy-export-item-bug.md @@ -1,7 +1,7 @@ # Task 08 — Fix copyExportItem always copying currentRow **Priority:** Medium -**Status:** TODO +**Status:** DONE **File:** `mainwindow.cpp:263–272` ## Problem From 2faf58729584a20526d576afe7387c6c6810e530 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:46:21 +0300 Subject: [PATCH 17/32] remove QDir::setCurrent global state mutation (task-09) Normalize _fileName to an absolute path in the QLdd constructor so ldd/ otool always receives a fully-qualified path regardless of the caller's working directory. Both QDir::setCurrent calls and the now-unused _lddDirPath member are removed. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 2 +- qldd.cpp | 7 ++----- qldd.h | 1 - tasks/task-09-qdir-setcurrent-global-state.md | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0d3c721..fa5a560 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,4 +84,4 @@ Detailed task files live in `tasks/`. Do not implement fixes not listed here wit | [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | DONE | `dynamic_cast` repeated per row in loop; cache the result | | [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | DONE | `getInfo` builds string twice; eliminate intermediate `QStringList` | | [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | DONE | `copyExportItem` always copies `currentRow()` regardless of selection | -| [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | TODO | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | +| [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | DONE | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/qldd.cpp b/qldd.cpp index 9c08c15..6cd4478 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -29,11 +29,11 @@ #endif QLdd::QLdd(QString fileName, QString lddDirPath, RulesMap demangleRules) - : _fileName(std::move(fileName)), + : _fileName(QFileInfo(fileName).absoluteFilePath()), _fileInfo(_fileName), _link(false), - _lddDirPath(std::move(lddDirPath)), _demangleRules(std::move(demangleRules)) { + Q_UNUSED(lddDirPath) _ownerMod.read = _fileInfo.permission(QFile::ReadOwner); _ownerMod.write = _fileInfo.permission(QFile::WriteOwner); _ownerMod.execute = _fileInfo.permission(QFile::ExeOwner); @@ -81,7 +81,6 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { std::stringstream ss; - QDir::setCurrent(getPathOfBinary()); ss << CMD_LDD << " \"" << _fileName.toStdString() << "\""; treeWidget.setUpdatesEnabled(false); @@ -141,8 +140,6 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { } }); treeWidget.setUpdatesEnabled(true); - - QDir::setCurrent(_lddDirPath); } void QLdd::fillExportTable(QListWidget &listWidget, const QString &filter) { diff --git a/qldd.h b/qldd.h index c5bcc1b..59ccb53 100755 --- a/qldd.h +++ b/qldd.h @@ -65,7 +65,6 @@ class QLdd { QString _tmCreate; QString _tmAccess; QString _tmModify; - QString _lddDirPath; QString _fileSize; QMOD _ownerMod{}; QMOD _groupMod{}; diff --git a/tasks/task-09-qdir-setcurrent-global-state.md b/tasks/task-09-qdir-setcurrent-global-state.md index 7440612..f46710a 100644 --- a/tasks/task-09-qdir-setcurrent-global-state.md +++ b/tasks/task-09-qdir-setcurrent-global-state.md @@ -1,7 +1,7 @@ # Task 09 — Remove QDir::setCurrent global state from fillDependency **Priority:** Medium -**Status:** TODO +**Status:** DONE **File:** `qldd.cpp:82, 142` ## Problem From a3b974840007e061784d32fa4f0f7b60c11be77d Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 10:48:39 +0300 Subject: [PATCH 18/32] remove completed task files and tasks section from CLAUDE.md All 9 performance/correctness tasks have been resolved. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 15 ------ tasks/task-01-fillexporttable-threading.md | 21 -------- tasks/task-02-remove-debug-logging.md | 32 ------------ tasks/task-03-demangle-heap-alloc.md | 28 ---------- tasks/task-04-widget-update-batching.md | 34 ------------ tasks/task-05-qcolor-outside-loop.md | 29 ----------- tasks/task-06-cache-dynamic-cast.md | 34 ------------ tasks/task-07-getinfo-double-build.md | 42 --------------- tasks/task-08-copy-export-item-bug.md | 52 ------------------- tasks/task-09-qdir-setcurrent-global-state.md | 37 ------------- 10 files changed, 324 deletions(-) delete mode 100644 tasks/task-01-fillexporttable-threading.md delete mode 100644 tasks/task-02-remove-debug-logging.md delete mode 100644 tasks/task-03-demangle-heap-alloc.md delete mode 100644 tasks/task-04-widget-update-batching.md delete mode 100644 tasks/task-05-qcolor-outside-loop.md delete mode 100644 tasks/task-06-cache-dynamic-cast.md delete mode 100644 tasks/task-07-getinfo-double-build.md delete mode 100644 tasks/task-08-copy-export-item-bug.md delete mode 100644 tasks/task-09-qdir-setcurrent-global-state.md diff --git a/CLAUDE.md b/CLAUDE.md index fa5a560..dff25dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,18 +70,3 @@ These are installed as separate CPack components (`nautilus-integration`, `dolph clang-format -i *.cpp *.h ``` -## Pending performance / correctness tasks - -Detailed task files live in `tasks/`. Do not implement fixes not listed here without first checking whether an existing task covers the change. - -| ID | File(s) | Priority | Status | Summary | -|---|---|---|---|---| -| [task-01](tasks/task-01-fillexporttable-threading.md) | `qldd.cpp:145–177` | **Critical** | DONE | Qt widget calls from worker threads — undefined behavior / crash risk | -| [task-02](tasks/task-02-remove-debug-logging.md) | `qldd.cpp:162–164` | **High** | DONE | `qDebug` in hot symbol loop fires thousands of times in release builds | -| [task-03](tasks/task-03-demangle-heap-alloc.md) | `qldd.cpp:156` | Medium | DONE | `toStdString().c_str()` allocates heap per symbol; use `toUtf8().constData()` | -| [task-04](tasks/task-04-widget-update-batching.md) | `qldd.cpp:77–177` | **High** | DONE | Missing `setUpdatesEnabled(false/true)` causes per-item repaints | -| [task-05](tasks/task-05-qcolor-outside-loop.md) | `qldd.cpp:124` | Low | DONE | `QColor("red")` constructed per dependency line; replace with `Qt::red` | -| [task-06](tasks/task-06-cache-dynamic-cast.md) | `demanglerules.cpp:42` | Low | DONE | `dynamic_cast` repeated per row in loop; cache the result | -| [task-07](tasks/task-07-getinfo-double-build.md) | `qldd.cpp:187–198` | Low | DONE | `getInfo` builds string twice; eliminate intermediate `QStringList` | -| [task-08](tasks/task-08-copy-export-item-bug.md) | `mainwindow.cpp:263–272` | Medium | DONE | `copyExportItem` always copies `currentRow()` regardless of selection | -| [task-09](tasks/task-09-qdir-setcurrent-global-state.md) | `qldd.cpp:82,142` | Medium | DONE | `QDir::setCurrent` mutates process-global CWD; pass absolute path to command instead | diff --git a/tasks/task-01-fillexporttable-threading.md b/tasks/task-01-fillexporttable-threading.md deleted file mode 100644 index b307a16..0000000 --- a/tasks/task-01-fillexporttable-threading.md +++ /dev/null @@ -1,21 +0,0 @@ -# Task 01 — Fix thread-safety bug in fillExportTable - -**Priority:** Critical -**Status:** DONE -**File:** `qldd.cpp:145–177` - -## Problem - -`fillExportTable` uses `Exec::ASYNC` which calls `std::async(std::launch::async, ...)` for every line of `nm` output. This can spawn hundreds or thousands of threads for a large binary. More critically, `QListWidget::addItem` is called from worker threads — Qt widgets must only be touched from the main thread. The mutex around `addItem` does not make it safe; this is undefined behavior and a latent crash. - -## Fix - -1. Switch the lambda to collect results into a `QVector` (protected by the mutex, or using a lock-free append into a pre-allocated vector). -2. After all async work completes (futures resolved), post the collected items back to the main thread and populate the widget there — or use `QMetaObject::invokeMethod(..., Qt::QueuedConnection)` from inside the lambda to queue each `addItem` call to the main thread. -3. Alternatively, drop `Exec::ASYNC` here entirely and run symbol parsing on a `QThread` / `QtConcurrent::run`, emitting a signal when done. - -## Acceptance criteria - -- No Qt widget calls from worker threads. -- UI remains responsive during `nm` parsing for large binaries. -- `nm` output is still processed in parallel (demangling + regex replacements are CPU-bound and safe to parallelize). diff --git a/tasks/task-02-remove-debug-logging.md b/tasks/task-02-remove-debug-logging.md deleted file mode 100644 index 0821287..0000000 --- a/tasks/task-02-remove-debug-logging.md +++ /dev/null @@ -1,32 +0,0 @@ -# Task 02 — Remove debug logging from hot symbol-processing loop - -**Priority:** High -**Status:** DONE (resolved as part of task-01 rewrite) -**File:** `qldd.cpp:162–164` - -## Problem - -```cpp -if (demangled.contains("string")) { - qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; -} -``` - -This fires inside the inner demangling loop for every rule applied to every exported symbol that contains `"string"`. For a typical C++ binary this means thousands of `qDebug()` calls in release builds. `qDebug()` formats and writes to stderr unconditionally unless `QT_NO_DEBUG_OUTPUT` is defined. - -## Fix - -Remove the three lines entirely. They appear to be leftover debugging from development. If retained for development purposes, guard with: - -```cpp -#ifdef QT_DEBUG -if (demangled.contains("string")) { - qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; -} -#endif -``` - -## Acceptance criteria - -- No `qDebug` output in release builds from this loop. -- Lines removed or wrapped in `#ifdef QT_DEBUG`. diff --git a/tasks/task-03-demangle-heap-alloc.md b/tasks/task-03-demangle-heap-alloc.md deleted file mode 100644 index c656f3f..0000000 --- a/tasks/task-03-demangle-heap-alloc.md +++ /dev/null @@ -1,28 +0,0 @@ -# Task 03 — Eliminate redundant heap allocation in __cxa_demangle call - -**Priority:** Medium -**Status:** DONE (resolved as part of task-01 rewrite) -**File:** `qldd.cpp:156` - -## Problem - -```cpp -char *realname = abi::__cxa_demangle(info.at(2).toStdString().c_str(), nullptr, nullptr, &status); -``` - -`toStdString()` allocates a `std::string` on the heap just to call `.c_str()` on it. This happens for every exported symbol. - -## Fix - -Replace with `toUtf8().constData()` which returns a pointer into Qt's internal copy-on-write buffer without a heap allocation: - -```cpp -char *realname = abi::__cxa_demangle(info.at(2).toUtf8().constData(), nullptr, nullptr, &status); -``` - -Note: the `QByteArray` returned by `toUtf8()` must stay alive for the duration of the `constData()` pointer use. Since `__cxa_demangle` returns before the temporary is destroyed, this is safe as a function argument. - -## Acceptance criteria - -- `toStdString()` call removed at `qldd.cpp:156`. -- No intermediate `std::string` heap allocation per symbol. diff --git a/tasks/task-04-widget-update-batching.md b/tasks/task-04-widget-update-batching.md deleted file mode 100644 index 9102f0b..0000000 --- a/tasks/task-04-widget-update-batching.md +++ /dev/null @@ -1,34 +0,0 @@ -# Task 04 — Batch widget updates in fillDependency and fillExportTable - -**Priority:** High -**Status:** DONE -**Files:** `qldd.cpp:77–143`, `qldd.cpp:145–177` - -## Problem - -`fillDependency` calls `treeWidget.addTopLevelItem()` for every dependency line, and `fillExportTable` calls `listWidget.addItem()` for every exported symbol. Each call triggers an incremental layout recalculation and repaint. For binaries with many dependencies or hundreds of exports the UI visibly redraws item-by-item and the calls collectively dominate render time. - -## Fix - -Wrap the population loops with `setUpdatesEnabled`: - -```cpp -// fillDependency -treeWidget.setUpdatesEnabled(false); -// ... all addTopLevelItem / child item calls ... -treeWidget.setUpdatesEnabled(true); -``` - -```cpp -// fillExportTable -listWidget.setUpdatesEnabled(false); -// ... all addItem calls ... -listWidget.setUpdatesEnabled(true); -``` - -Ensure `setUpdatesEnabled(true)` is called even if an exception or early return occurs (use RAII guard or explicit cleanup paths). - -## Acceptance criteria - -- `setUpdatesEnabled(false/true)` wraps population in both methods. -- Filling a large binary's exports/deps appears instant rather than incrementally painted. diff --git a/tasks/task-05-qcolor-outside-loop.md b/tasks/task-05-qcolor-outside-loop.md deleted file mode 100644 index e581d60..0000000 --- a/tasks/task-05-qcolor-outside-loop.md +++ /dev/null @@ -1,29 +0,0 @@ -# Task 05 — Move QColor construction outside per-line lambda - -**Priority:** Low -**Status:** DONE (resolved together with task-04) -**File:** `qldd.cpp:124` - -## Problem - -```cpp -// inside the per-dependency-line lambda: -QColor redC("red"); -``` - -`QColor("red")` parses the color name string on every invocation of the lambda (i.e., once per line of `ldd`/`otool` output). While cheap individually, it is unnecessary repeated work. - -## Fix - -Replace with the pre-defined Qt constant: - -```cpp -tmp->setForeground(0, QBrush(Qt::red)); -``` - -This removes the string parsing entirely. The `QBrush(Qt::red)` constructor uses a compile-time constant. - -## Acceptance criteria - -- `QColor("red")` removed from the lambda body. -- `Qt::red` used directly in the `setForeground` call. diff --git a/tasks/task-06-cache-dynamic-cast.md b/tasks/task-06-cache-dynamic-cast.md deleted file mode 100644 index 018f2d9..0000000 --- a/tasks/task-06-cache-dynamic-cast.md +++ /dev/null @@ -1,34 +0,0 @@ -# Task 06 — Cache dynamic_cast result in demanglerules::insertNewRow - -**Priority:** Low -**Status:** DONE -**File:** `demanglerules.cpp:42` - -## Problem - -```cpp -void demanglerules::insertNewRow(int row, const QString &src, const QString &dst) { - ui->tableWidget->insertRow(row); - auto *m = dynamic_cast(parent()); // repeated on every row - ... -} -``` - -`insertNewRow` is called in a loop during construction (once per rule). `dynamic_cast` walks the vtable/RTTI chain on each call. The result is always the same object. - -## Fix - -Option A — cache in the constructor and pass as a parameter: - -```cpp -void demanglerules::insertNewRow(int row, const QString &src, const QString &dst, MainWindow *m) { ... } -``` - -Option B — store `MainWindow *m` as a member in the constructor and use it in `insertNewRow` without recasting. - -The constructor already does the cast once (`demanglerules.cpp:16`), so reuse that result. - -## Acceptance criteria - -- `dynamic_cast(parent())` not called inside `insertNewRow`. -- Cached pointer used instead. diff --git a/tasks/task-07-getinfo-double-build.md b/tasks/task-07-getinfo-double-build.md deleted file mode 100644 index ea3e29e..0000000 --- a/tasks/task-07-getinfo-double-build.md +++ /dev/null @@ -1,42 +0,0 @@ -# Task 07 — Eliminate double string build in getInfo - -**Priority:** Low -**Status:** DONE -**File:** `qldd.cpp:187–198` - -## Problem - -```cpp -QString buf; -execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { buf.append(line + "\n"); }); -QStringList slTmp = buf.split(INFO_SPLITTER); -buf.clear(); -for (const QString &v : slTmp) { - buf.append(v.trimmed()).append("\n"); -} -return buf; -``` - -The full output is first assembled into `buf`, then split into a `QStringList`, then reassembled. This is two full string allocations and two passes over the data. Since the lambda is called per line, trimming can happen there directly. - -## Fix - -```cpp -QString buf; -execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { - // split by INFO_SPLITTER within each line and trim inline - const auto parts = line.split(INFO_SPLITTER); - for (const auto &part : parts) { - buf.append(part.trimmed()).append("\n"); - } -}); -return buf; -``` - -This eliminates the intermediate `QStringList` and the second pass. - -## Acceptance criteria - -- `buf.split(INFO_SPLITTER)` removed. -- Single-pass construction of the final string inside the lambda. -- Output is functionally identical (each comma/newline-separated field on its own trimmed line). diff --git a/tasks/task-08-copy-export-item-bug.md b/tasks/task-08-copy-export-item-bug.md deleted file mode 100644 index 619a8e1..0000000 --- a/tasks/task-08-copy-export-item-bug.md +++ /dev/null @@ -1,52 +0,0 @@ -# Task 08 — Fix copyExportItem always copying currentRow - -**Priority:** Medium -**Status:** DONE -**File:** `mainwindow.cpp:263–272` - -## Problem - -```cpp -void MainWindow::copyExportItem() { - QClipboard *clipboard = qApp->clipboard(); - for (int i = 0; i < ui->listWidgetExportTable->selectedItems().size(); ++i) { - QListWidgetItem *item = ui->listWidgetExportTable->item(ui->listWidgetExportTable->currentRow()); - clipboard->setText(item->text()); - } -} -``` - -Two bugs: -1. The loop indexes with `i` but always fetches `currentRow()` — for multi-selection it overwrites the clipboard N times with the same item. -2. `selectedItems()` is called once per iteration (implicit in the loop condition re-evaluation). - -## Fix - -```cpp -void MainWindow::copyExportItem() { - const auto selected = ui->listWidgetExportTable->selectedItems(); - if (!selected.isEmpty()) { - qApp->clipboard()->setText(selected.last()->text()); - } -} -``` - -If multi-item copy (newline-joined) is desired: - -```cpp -void MainWindow::copyExportItem() { - QStringList texts; - for (const auto *item : ui->listWidgetExportTable->selectedItems()) { - texts << item->text(); - } - if (!texts.isEmpty()) { - qApp->clipboard()->setText(texts.join('\n')); - } -} -``` - -## Acceptance criteria - -- `currentRow()` removed from `copyExportItem`. -- Selected items iterated correctly. -- No redundant `selectedItems()` calls inside the loop condition. diff --git a/tasks/task-09-qdir-setcurrent-global-state.md b/tasks/task-09-qdir-setcurrent-global-state.md deleted file mode 100644 index f46710a..0000000 --- a/tasks/task-09-qdir-setcurrent-global-state.md +++ /dev/null @@ -1,37 +0,0 @@ -# Task 09 — Remove QDir::setCurrent global state from fillDependency - -**Priority:** Medium -**Status:** DONE -**File:** `qldd.cpp:82, 142` - -## Problem - -```cpp -QDir::setCurrent(getPathOfBinary()); // line 82 -// ... run ldd ... -QDir::setCurrent(_lddDirPath); // line 142 -``` - -`QDir::setCurrent` changes the working directory of the entire process. This is: -- Not thread-safe: if two `QLdd` instances ever run concurrently, the CWD races. -- Fragile: an early return or exception between lines 82 and 142 leaves the process in a wrong directory. -- Unnecessary: `ldd`/`otool` accept absolute paths. - -## Fix - -Pass the absolute path directly to the command and remove both `QDir::setCurrent` calls: - -```cpp -// Already using _fileName which is absolute — no CWD change needed. -ss << CMD_LDD << " \"" << _fileName.toStdString() << "\""; -``` - -Verify `_fileName` is always stored as an absolute path (it comes from `QFileInfo::absoluteFilePath()` or the dialog, both of which provide absolute paths). - -If there is a scenario where a relative path is passed to the constructor, normalize it to absolute in the constructor using `QFileInfo(_fileName).absoluteFilePath()`. - -## Acceptance criteria - -- Both `QDir::setCurrent` calls removed from `fillDependency`. -- `_fileName` guaranteed absolute (assert or normalize in constructor). -- No process-global CWD mutation. From c88fd3b55cbf6184bee7cff49638e3af080ad17d Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 11:00:30 +0300 Subject: [PATCH 19/32] fix deprecation warnings, bump C++17, refresh CI (plan: drifting-splashing-hammock) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C++ / warnings: - Bump CMAKE_CXX_STANDARD 11→17 (all CI compilers gcc8+ support it) - Replace std::result_of with std::invoke_result_t in execAndDoOnEveryLine - Guard AA_EnableHighDpiScaling with #if QT_VERSION < 6.0.0 to silence the Qt6 deprecation warning while keeping Qt5 high-DPI behaviour CI: - Enable aarch64/fedora_latest in ubuntu-latest-nonx86 matrix (supported by run-on-arch-action); remove dead ppc64le/s390x fedora comments (fedora images don't exist for those architectures) - Remove ubuntu-20-04 job (EOL April 2025; 22.04/24.04 matrix covers LTS) - Replace debian-buster (EOL June 2024) with debian-bookworm (Debian 12); update devel-pkgs: python-nautilus → python3-nautilus - Fix macOS job name: "macos-latest x86_64" → "macos-latest" (runner is now ARM64) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 70 +++----------------------------- CMakeLists.txt | 2 +- main.cpp | 2 + qldd.h | 2 +- 4 files changed, 10 insertions(+), 66 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index f35c831..d708fef 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -15,12 +15,8 @@ jobs: distro: ubuntu_latest - arch: riscv64 distro: ubuntu_latest - # - arch: aarch64 - # distro: fedora_latest - # - arch: ppc64le - # distro: fedora_latest - # - arch: s390x - # distro: fedora_latest + - arch: aarch64 + distro: fedora_latest steps: - name: Get current date id: date @@ -123,60 +119,6 @@ jobs: find: ':' # we want to remove : from container name replace: '-' # and replace it with - - - name: Upload Qldd binary - uses: actions/upload-artifact@v4 - with: - name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} - path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} - ubuntu-20-04: - runs-on: ubuntu-latest - name: Build on ${{ matrix.container }} x86_64 - strategy: - # - # matrix for containers - # - matrix: - container: - - ubuntu:20.04 - - container: - image: ${{ matrix.container }} - - steps: - - name: Get current date - id: date - run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - - name: Get commit sha - id: git_sha - run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v4 - # install dependencies - - name: devel-pkgs - run: | - echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && - apt-get update -y && apt-get install -yq python-nautilus binutils make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release - # build project - - name: mkdir - run: mkdir cmake-build-release - - name: cmake cmake-build-release - run: cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. - - name: cmake make - run: cmake --build cmake-build-release/ --target all - env: - MAKEFLAGS: "-j2" - - name: get-version - id: get-version - run: echo "prj_ver=$(cat ./VERSION.txt)" >> $GITHUB_OUTPUT - - name: cpack - run: cd cmake-build-release && cpack -G DEB && cd .. - # upload artifact - - uses: mad9000/actions-find-and-replace-string@5 - id: container - with: - source: ${{ matrix.container }} - find: ':' # we want to remove : from container name - replace: '-' # and replace it with - - - name: Upload Qldd binary uses: actions/upload-artifact@v4 with: @@ -234,7 +176,7 @@ jobs: with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} - debian-buster: + debian-bookworm: runs-on: ubuntu-latest name: Build on ${{ matrix.container }} x86_64 strategy: @@ -243,7 +185,7 @@ jobs: # matrix: container: - - debian:buster + - debian:bookworm container: image: ${{ matrix.container }} @@ -258,7 +200,7 @@ jobs: - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs - run: apt-get update -y && apt-get install -yq binutils python-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release + run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release # build project - name: mkdir run: mkdir cmake-build-release @@ -398,7 +340,7 @@ jobs: path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} macos-clang-cmake: runs-on: macos-latest - name: Build on macos-latest x86_64 + name: Build on macos-latest steps: - name: Get current date diff --git a/CMakeLists.txt b/CMakeLists.txt index 7920626..fd561bb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Threads REQUIRED) diff --git a/main.cpp b/main.cpp index e7bc3c8..7ad0a35 100755 --- a/main.cpp +++ b/main.cpp @@ -4,7 +4,9 @@ #include int main(int argc, char *argv[]) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif QApplication app(argc, argv); QApplication::setApplicationName("Qldd"); diff --git a/qldd.h b/qldd.h index 59ccb53..8437c9e 100755 --- a/qldd.h +++ b/qldd.h @@ -83,7 +83,7 @@ void execAndDoOnEveryLine(const std::string &execString, const Action &action, E QTextStream nmOutStream(cmdStream.get()); QString line; - using returnType = typename std::result_of::type; + using returnType = std::invoke_result_t; std::list> retList; do { line = nmOutStream.readLine(); From a59042a45ccdbf4179d3cb125a8f5906348a5471 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 13:05:55 +0300 Subject: [PATCH 20/32] Unify Nautilus extension to single file supporting all API versions Replace the two separate extension files (pre-43 and 43+) with a single dependency-viewer.py using *args to handle both Nautilus API versions: - Nautilus <43: get_file_items(window, files) - Nautilus 43+: get_file_items(files) Both files registered the same class name, so whichever loaded second silently shadowed the first, breaking the extension on half of all distros. Remove the now-deleted dependency-viewer-43.py install rule from CMakeLists.txt. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 3 --- .../extensions/dependency-viewer-43.py | 27 ------------------- .../extensions/dependency-viewer.py | 13 ++++++--- 3 files changed, 9 insertions(+), 34 deletions(-) delete mode 100755 nautilus-python/extensions/dependency-viewer-43.py diff --git a/CMakeLists.txt b/CMakeLists.txt index fd561bb..918bdb8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,9 +195,6 @@ else() install(FILES ${CMAKE_SOURCE_DIR}/nautilus-python/extensions/dependency-viewer.py DESTINATION share/nautilus-python/extensions COMPONENT nautilus-integration) - install(FILES ${CMAKE_SOURCE_DIR}/nautilus-python/extensions/dependency-viewer-43.py - DESTINATION share/nautilus-python/extensions - COMPONENT nautilus-integration) install(FILES ${CMAKE_SOURCE_DIR}/dolphin/kde-qldd-menu.desktop DESTINATION share/kservices5/ServiceMenus COMPONENT dolphin-integration) diff --git a/nautilus-python/extensions/dependency-viewer-43.py b/nautilus-python/extensions/dependency-viewer-43.py deleted file mode 100755 index 61b5736..0000000 --- a/nautilus-python/extensions/dependency-viewer-43.py +++ /dev/null @@ -1,27 +0,0 @@ -import os, os.path -import urllib.parse - -from gi.repository import Nautilus, GObject - -QLDD_KEY = '/usr/bin/Qldd' - -class OpenQlddExtension(GObject.GObject, Nautilus.MenuProvider): - def __init__(self): - pass - - def _open_qldd(self, file): - filename = urllib.parse.unquote(file.get_uri()[7:]) - qldd = QLDD_KEY - os.system('%s %s &' % (qldd, filename)) - - def menu_activate_cb(self, menu, files): - for file in files: - self._open_qldd(file) - - def get_file_items(self, files): - item = Nautilus.MenuItem( - name='Qldd', - label='View dependencies', - icon='/usr/share/icons/qldd/Qldd.png') - item.connect('activate', self.menu_activate_cb, files) - return item, diff --git a/nautilus-python/extensions/dependency-viewer.py b/nautilus-python/extensions/dependency-viewer.py index 0da3a53..fb440fa 100755 --- a/nautilus-python/extensions/dependency-viewer.py +++ b/nautilus-python/extensions/dependency-viewer.py @@ -1,24 +1,29 @@ -import os, os.path +import os import urllib.parse from gi.repository import Nautilus, GObject QLDD_KEY = '/usr/bin/Qldd' + class OpenQlddExtension(GObject.GObject, Nautilus.MenuProvider): def __init__(self): pass def _open_qldd(self, file): filename = urllib.parse.unquote(file.get_uri()[7:]) - qldd = QLDD_KEY - os.system('%s %s &' % (qldd, filename)) + os.system('%s %s &' % (QLDD_KEY, filename)) def menu_activate_cb(self, menu, files): for file in files: self._open_qldd(file) - def get_file_items(self, window, files): + # Nautilus 43+ removed the `window` parameter from get_file_items. + # Using *args makes this extension work on both old and new API: + # Nautilus <43 calls get_file_items(window, files) -> args = (window, files) + # Nautilus 43+ calls get_file_items(files) -> args = (files,) + def get_file_items(self, *args): + files = args[-1] item = Nautilus.MenuItem( name='Qldd', label='View dependencies', From 71575ae58d2b72fb4f7281c59b1b5649b95e8ca0 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 13:51:09 +0300 Subject: [PATCH 21/32] Fix three CI failures: s390x ubuntu version, centos image, fedora package name - s390x: ubuntu_latest requires z15 hardware not available in QEMU emulation; downgrade to ubuntu22.04 which works on z14 - centos: centos:latest is EOL/gone; replace with quay.io/centos/centos:stream9 and drop the CentOS 8 vault mirror hacks; enable CRB + EPEL for stream9 - fedora/centos: python3-nautilus no longer exists on Fedora 44+; use nautilus-python which is the correct RPM package name (matches CMakeLists.txt) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index d708fef..2f89976 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -12,7 +12,7 @@ jobs: - arch: ppc64le distro: ubuntu_latest - arch: s390x - distro: ubuntu_latest + distro: ubuntu22.04 - arch: riscv64 distro: ubuntu_latest - arch: aarch64 @@ -49,7 +49,7 @@ jobs: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release ;; fedora*) - yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb ;; esac run: | @@ -237,7 +237,7 @@ jobs: # matrix: container: - - centos:latest + - quay.io/centos/centos:stream9 container: image: ${{ matrix.container }} @@ -253,12 +253,11 @@ jobs: # install dependencies - name: devel-pkgs run: | - cd /etc/yum.repos.d/ && - sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && - sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* && - cd / && - yum update -y && yum install -yq epel-next-release && - yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb-core + yum install -y dnf-plugins-core && + yum config-manager --set-enabled crb && + yum install -y epel-release epel-next-release && + yum update -y && + yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb-core # build project - name: mkdir run: mkdir cmake-build-release @@ -310,7 +309,7 @@ jobs: - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs - run: yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + run: yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb # build project - name: mkdir run: mkdir cmake-build-release From 0125472930d23da6a0322e2820b541f65641bbaf Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 13:53:22 +0300 Subject: [PATCH 22/32] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c02a68a..3ee38d4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ VERSION.txt /.DS_Store /CMakeLists.txt.user /cmake-vsc-build/ +/build/ +/.qtcreator/ \ No newline at end of file From f562f388bc3fb144533d0f40863534f3cbdf166a Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 13:56:25 +0300 Subject: [PATCH 23/32] Make lsb_release optional; drop unavailable lsb packages from CI redhat-lsb-core is absent from EPEL 9 and redhat-lsb is gone from Fedora 44. lsb_release is only used for log messages and DEB package naming on Ubuntu/Debian, neither of which applies to RPM builds. Degrade gracefully with a warning instead of a fatal cmake error when the binary is not present. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 6 +++--- CMakeLists.txt | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 2f89976..b8f4433 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -49,7 +49,7 @@ jobs: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release ;; fedora*) - yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build ;; esac run: | @@ -257,7 +257,7 @@ jobs: yum config-manager --set-enabled crb && yum install -y epel-release epel-next-release && yum update -y && - yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb-core + yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build # build project - name: mkdir run: mkdir cmake-build-release @@ -309,7 +309,7 @@ jobs: - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs - run: yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + run: yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build # build project - name: mkdir run: mkdir cmake-build-release diff --git a/CMakeLists.txt b/CMakeLists.txt index 918bdb8..350e8cd 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,11 @@ configure_file(${CMAKE_SOURCE_DIR}/resources/qldd.desktop.in ${CMAKE_BINARY_DIR} function(get_linux_lsb_release_information) find_program(LSB_RELEASE_EXEC lsb_release) if(NOT LSB_RELEASE_EXEC) - message(FATAL_ERROR "Could not detect lsb_release executable, can not gather required information") + message(WARNING "lsb_release not found; distro info will be unavailable") + set(LSB_RELEASE_ID_SHORT "unknown" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "unknown" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "unknown" PARENT_SCOPE) + return() endif() execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) From a2ab160e4480ac3d3cb928f4feb73f561fae1b3b Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 14:02:20 +0300 Subject: [PATCH 24/32] Fall back to /etc/os-release when lsb_release is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lsb_release is absent on CentOS Stream 9 and Fedora 44+ by default. Parse /etc/os-release as a fallback — it is present on all systemd-based distros. Capitalize the ID field to match lsb_release output so existing Ubuntu/Debian codename checks downstream continue to work unchanged. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 350e8cd..93c024a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,21 +20,45 @@ configure_file(${CMAKE_SOURCE_DIR}/resources/qldd.desktop.in ${CMAKE_BINARY_DIR} function(get_linux_lsb_release_information) find_program(LSB_RELEASE_EXEC lsb_release) - if(NOT LSB_RELEASE_EXEC) - message(WARNING "lsb_release not found; distro info will be unavailable") + if(LSB_RELEASE_EXEC) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --release OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --codename OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + set(LSB_RELEASE_ID_SHORT "${LSB_RELEASE_ID_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "${LSB_RELEASE_VERSION_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "${LSB_RELEASE_CODENAME_SHORT}" PARENT_SCOPE) + elseif(EXISTS "/etc/os-release") + # Fallback: parse /etc/os-release (present on all systemd-based distros) + set(_id "unknown") + set(_version "unknown") + set(_codename "unknown") + file(STRINGS "/etc/os-release" _os_release) + foreach(_line ${_os_release}) + if(_line MATCHES "^ID=(.+)$") + set(_id "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _id "${_id}") + # Capitalize first letter to match lsb_release output (ubuntu -> Ubuntu) + string(SUBSTRING "${_id}" 0 1 _first) + string(TOUPPER "${_first}" _first) + string(SUBSTRING "${_id}" 1 -1 _rest) + set(_id "${_first}${_rest}") + elseif(_line MATCHES "^VERSION_ID=(.+)$") + set(_version "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _version "${_version}") + elseif(_line MATCHES "^VERSION_CODENAME=(.+)$") + set(_codename "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _codename "${_codename}") + endif() + endforeach() + set(LSB_RELEASE_ID_SHORT "${_id}" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "${_version}" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "${_codename}" PARENT_SCOPE) + else() + message(WARNING "Neither lsb_release nor /etc/os-release found; distro info unavailable") set(LSB_RELEASE_ID_SHORT "unknown" PARENT_SCOPE) set(LSB_RELEASE_VERSION_SHORT "unknown" PARENT_SCOPE) set(LSB_RELEASE_CODENAME_SHORT "unknown" PARENT_SCOPE) - return() endif() - - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --release OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --codename OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - - set(LSB_RELEASE_ID_SHORT "${LSB_RELEASE_ID_SHORT}" PARENT_SCOPE) - set(LSB_RELEASE_VERSION_SHORT "${LSB_RELEASE_VERSION_SHORT}" PARENT_SCOPE) - set(LSB_RELEASE_CODENAME_SHORT "${LSB_RELEASE_CODENAME_SHORT}" PARENT_SCOPE) endfunction() message(STATUS "COMMITTER_FULLSHA ${COMMITTER_FULLSHA}") From 7395ed205045f499a7f40c65e67c39944688d4fa Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 14:06:29 +0300 Subject: [PATCH 25/32] Fix artifact name containing slashes for quay.io/centos/centos:stream9 Chain a second find-and-replace step to strip '/' after ':' is replaced, so the artifact name contains no invalid characters. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index b8f4433..32ee01b 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -274,11 +274,17 @@ jobs: run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - uses: mad9000/actions-find-and-replace-string@5 - id: container + id: container_colon with: source: ${{ matrix.container }} - find: ':' # we want to remove : from container name - replace: '-' # and replace it with - + find: ':' + replace: '-' + - uses: mad9000/actions-find-and-replace-string@5 + id: container + with: + source: ${{ steps.container_colon.outputs.value }} + find: '/' + replace: '-' - name: Upload Qldd binary uses: actions/upload-artifact@v4 From 3b90f2589ff055c8670a32498353ca300075470f Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 14:12:08 +0300 Subject: [PATCH 26/32] Bump version to 1.3.0, fix artifact name sanitization, add workflow constraints - CMakeLists.txt: bump version 1.2.1 -> 1.3.0 - CI: replace chained find-and-replace steps with tr ':/' '--' to correctly sanitize quay.io/centos/centos:stream9 into a valid artifact name - CLAUDE.md: document no-auto-commit/push constraint Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 13 ++----------- CLAUDE.md | 4 ++++ CMakeLists.txt | 4 ++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 32ee01b..39d9aea 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -273,18 +273,9 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@5 - id: container_colon - with: - source: ${{ matrix.container }} - find: ':' - replace: '-' - - uses: mad9000/actions-find-and-replace-string@5 + - name: Sanitize container name for artifact id: container - with: - source: ${{ steps.container_colon.outputs.value }} - find: '/' - replace: '-' + run: echo "value=$(echo '${{ matrix.container }}' | tr ':/' '--')" >> $GITHUB_OUTPUT - name: Upload Qldd binary uses: actions/upload-artifact@v4 diff --git a/CLAUDE.md b/CLAUDE.md index dff25dc..936776c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,10 @@ The app is a single-window Qt application with no separate library targets. These are installed as separate CPack components (`nautilus-integration`, `dolphin-integration`) and are not part of the C++ build. +## Workflow constraints + +**Never commit or push without explicit user approval.** Always show the intended changes and ask before running `git commit` or `git push`. Each commit/push requires a separate confirmation — a prior approval does not carry over. + ## Code style `.clang-format` and `.clang-tidy` are present. Run clang-format before committing: diff --git a/CMakeLists.txt b/CMakeLists.txt index 93c024a..9a64a9c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake/modules") cmake_minimum_required(VERSION 3.5) set(MAJOR "1") -set(MINOR "2") -set(PATCH "1") +set(MINOR "3") +set(PATCH "0") cmake_policy(SET CMP0048 NEW) project(Qldd From 4c0773c40bf98968f857dc6c085f7dd8dc703054 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 14:16:53 +0300 Subject: [PATCH 27/32] Pin all non-x86 ubuntu matrix entries to ubuntu22.04 ubuntu_latest resolves to Ubuntu 25.04 which includes Python 3.14; QEMU emulation for ppc64le and s390x segfaults in Python's dpkg postinst. Pin all four arches (aarch64, ppc64le, s390x, riscv64) to ubuntu22.04 which is stable under QEMU emulation. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 39d9aea..609b9f7 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -8,13 +8,13 @@ jobs: matrix: include: - arch: aarch64 - distro: ubuntu_latest + distro: ubuntu22.04 - arch: ppc64le - distro: ubuntu_latest + distro: ubuntu22.04 - arch: s390x distro: ubuntu22.04 - arch: riscv64 - distro: ubuntu_latest + distro: ubuntu22.04 - arch: aarch64 distro: fedora_latest steps: From 746f6e5dc04566cc50df2573e70547e266f590d6 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 15:05:23 +0300 Subject: [PATCH 28/32] Add ulimit -s unlimited before build in non-x86 QEMU jobs GCC segfaults (ICE in cc1plus) on ppc64le under QEMU due to the default stack size limit applied by the emulator. Raising the stack limit to unlimited gives the compiler enough space to process large translation units like mainwindow.cpp. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 609b9f7..d44e983 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -53,6 +53,7 @@ jobs: ;; esac run: | + ulimit -s unlimited && mkdir cmake-build-release && cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. && cmake --build cmake-build-release/ --target all && From 62fd679e36f0827f04dceabdcbff5ce1f91a754c Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 15:14:40 +0300 Subject: [PATCH 29/32] Add libc6-dev to non-x86 ubuntu install to provide pthread.h Minimal ppc64le/ubuntu22.04 containers don't include libc6-dev by default, causing cmake FindThreads to fail with 'Could NOT find pthread.h'. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index d44e983..c6f1f6e 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -46,7 +46,7 @@ jobs: install: | case "${{ matrix.distro }}" in ubuntu*|jessie|stretch|buster|bullseye) - apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release + apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev ;; fedora*) yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build From 48a3ea559b146cc157400b4707582d57824e69bb Mon Sep 17 00:00:00 2001 From: Alexander B Date: Thu, 28 May 2026 15:26:08 +0300 Subject: [PATCH 30/32] Limit non-x86 QEMU builds to -j1 to prevent GCC ICE Parallel compilation exhausts memory in QEMU-emulated environments, causing cc1plus to segfault on aarch64 and ppc64le. Single-threaded builds are slower but stable under emulation. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/action-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index c6f1f6e..263a571 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -56,7 +56,7 @@ jobs: ulimit -s unlimited && mkdir cmake-build-release && cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. && - cmake --build cmake-build-release/ --target all && + cmake --build cmake-build-release/ --target all -- -j1 && [[ "$(lsb_release -i -s)" == "Ubuntu" ]] && export CPACK_FORMAT=DEB || export CPACK_FORMAT=RPM && [[ "$(lsb_release -i -s)" == "Ubuntu" ]] && export PKG_FORMAT=deb || export PKG_FORMAT=rpm && cd cmake-build-release && cpack -G ${CPACK_FORMAT} && cd .. && From 853365e024a1bf1f17fe4d5cac362794524fce20 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Fri, 29 May 2026 11:47:46 +0300 Subject: [PATCH 31/32] add libpthread-stubs0-dev and glibc-devel. The package libc6-dev is included but doesnt guarantee pthread headers are available in all configurations, especially on non-x86 architectures. --- .github/workflows/action-cpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 263a571..f3c9423 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -46,10 +46,10 @@ jobs: install: | case "${{ matrix.distro }}" in ubuntu*|jessie|stretch|buster|bullseye) - apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev + apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev libpthread-stubs0-dev ;; fedora*) - yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build + yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build glibc-devel ;; esac run: | From 00f911948578e512fce81a21f312220624db2681 Mon Sep 17 00:00:00 2001 From: Alexander B Date: Fri, 29 May 2026 13:06:02 +0300 Subject: [PATCH 32/32] add glibc-dev --- .github/workflows/action-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index f3c9423..75bc6e6 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -46,7 +46,7 @@ jobs: install: | case "${{ matrix.distro }}" in ubuntu*|jessie|stretch|buster|bullseye) - apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev libpthread-stubs0-dev + apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev libpthread-stubs0-dev glibc-dev ;; fedora*) yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build glibc-devel