ci: Add GitHub Actions workflow for build and release #277
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-windows: | |
| runs-on: windows-latest | |
| env: | |
| GSTREAMER_RELEASE_TAG: deps-gstreamer-1.22.8 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up MinGW | |
| uses: egor-tensin/setup-mingw@v2 | |
| with: | |
| version: 12.2.0 | |
| - name: Install Qt | |
| uses: jurplel/install-qt-action@v3 | |
| with: | |
| version: '6.4.2' | |
| host: 'windows' | |
| target: 'desktop' | |
| arch: 'win64_mingw' | |
| modules: 'qtmultimedia' | |
| - name: Download GStreamer (MinGW) from GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: pwsh | |
| run: | | |
| # Download MSI assets from your specific release "gstreamer-1.26.10" | |
| # NOTE: We assume the files attached are named: | |
| # gstreamer-1.0-mingw-x86_64-1.22.8.msi OR similar. | |
| # Since you mentioned the TAG is "gstreamer-1.26.10" but the files inside often follow standard naming. | |
| # I will attempt to download ALL msi files from that tag to be safe, then install them. | |
| gh release download gstreamer-1.26.10 --pattern "*.msi" --repo OpenIPC/dashboard | |
| # Identify filenames | |
| $msiRuntime = Get-ChildItem -Filter "gstreamer-1.0-mingw-*.msi" | Select-Object -ExpandProperty Name -First 1 | |
| $msiDevel = Get-ChildItem -Filter "gstreamer-1.0-devel-mingw-*.msi" | Select-Object -ExpandProperty Name -First 1 | |
| if (-not $msiRuntime -or -not $msiDevel) { | |
| Write-Error "Could not find both runtime and devel MSIs in the downloaded assets. Files found: $(Get-ChildItem *.msi | Out-String)" | |
| exit 1 | |
| } | |
| # Extract directly into the expected MinGW root to avoid nested paths | |
| $gstRoot = "C:\gstreamer\1.0\mingw_x86_64" | |
| New-Item -ItemType Directory -Force -Path $gstRoot | Out-Null | |
| Write-Host "Extracting Runtime (administrative install): $msiRuntime" | |
| Start-Process msiexec.exe -ArgumentList '/a', $msiRuntime, '/qn', "TARGETDIR=$gstRoot" -NoNewWindow -Wait | |
| Write-Host "Extracting Devel (administrative install): $msiDevel" | |
| Start-Process msiexec.exe -ArgumentList '/a', $msiDevel, '/qn', "TARGETDIR=$gstRoot" -NoNewWindow -Wait | |
| # Validate expected header exists | |
| $gstHeaderPath = "$gstRoot\include\gstreamer-1.0\gst\gst.h" | |
| if (-not (Test-Path $gstHeaderPath)) { | |
| Write-Host "WARNING: Expected gst.h not found at $gstHeaderPath. Searching C:\gstreamer..." | |
| if (-not (Test-Path "C:\gstreamer")) { | |
| Write-Error "CRITICAL: C:\gstreamer directory does not exist after extraction!" | |
| exit 1 | |
| } | |
| $gstHeader = Get-ChildItem -Recurse "C:\gstreamer" -Filter "gst.h" -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match "gstreamer-1.0\\gst\\gst\.h" } | | |
| Select-Object -First 1 | |
| if ($gstHeader) { | |
| # gst.h is in <root>\include\gstreamer-1.0\gst\gst.h | |
| # We need <root>, so go up 4 levels from gst.h | |
| $gstRoot = Split-Path (Split-Path (Split-Path (Split-Path $gstHeader.FullName -Parent) -Parent) -Parent) -Parent | |
| } else { | |
| Write-Error "Could not determine GStreamer installation root." | |
| Get-ChildItem -Recurse "C:\gstreamer" | Select-Object FullName -First 120 | |
| exit 1 | |
| } | |
| } | |
| if ($gstRoot -match "\\include$") { | |
| $gstRoot = Split-Path $gstRoot -Parent | |
| } | |
| Write-Host "GStreamer installed at: $gstRoot" | |
| echo "GSTREAMER_1_0_ROOT_MINGW_X86_64=$gstRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| echo "$gstRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| echo "PKG_CONFIG_PATH=$gstRoot\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| echo "Debug: Listing GStreamer Includes" | |
| Get-ChildItem -Recurse "$gstRoot\include" | Select-Object FullName -First 20 | |
| - name: Configure CMake | |
| run: | | |
| $qmake = (Get-Command qmake.exe -ErrorAction SilentlyContinue).Source | |
| if (-not $qmake) { $qmake = (Get-Command qmake -ErrorAction SilentlyContinue).Source } | |
| if (-not $qmake) { Write-Error "qmake not found in PATH"; exit 1 } | |
| $qtDir = Split-Path (Split-Path $qmake -Parent) -Parent | |
| cmake -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_PREFIX_PATH=$qtDir -DGStreamer_ROOT="C:/gstreamer/1.0/mingw_x86_64" | |
| - name: Build | |
| run: cmake --build build --config Release | |
| env: | |
| PKG_CONFIG_PATH: C:\gstreamer\1.0\mingw_x86_64\lib\pkgconfig | |
| - name: Upload CMake logs (Windows) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-cmake-logs | |
| path: | | |
| build/CMakeFiles/CMakeOutput.log | |
| build/CMakeFiles/CMakeError.log | |
| build/CMakeCache.txt | |
| build/compile_commands.json | |
| - name: Prepare Distribution Folder | |
| shell: pwsh | |
| run: | | |
| if (Test-Path dist) { Remove-Item -Recurse -Force dist } | |
| New-Item -ItemType Directory -Force -Path dist | |
| Copy-Item build/appOpenIPC-Dashboard.exe -Destination dist/ | |
| # Copy DLLs (Dahua, ONNX code in CMake might put them in build/) | |
| if (Test-Path build/*.dll) { | |
| Copy-Item build/*.dll -Destination dist/ -Force | |
| } | |
| # Resolve GStreamer root from env (set in install step), with fallback search | |
| $gstRoot = $env:GSTREAMER_1_0_ROOT_MINGW_X86_64 | |
| if (-not $gstRoot -or -not (Test-Path $gstRoot)) { | |
| $gstHeader = Get-ChildItem -Recurse "C:\gstreamer" -Filter "gst.h" -ErrorAction SilentlyContinue | | |
| Where-Object { $_.FullName -match "gstreamer-1.0\\gst\\gst\.h" } | | |
| Select-Object -First 1 | |
| if ($gstHeader) { | |
| $gstRoot = Split-Path (Split-Path (Split-Path (Split-Path $gstHeader.FullName -Parent) -Parent) -Parent) -Parent | |
| } | |
| } | |
| if (-not $gstRoot -or -not (Test-Path $gstRoot)) { | |
| Write-Error "GStreamer root not found. GSTREAMER_1_0_ROOT_MINGW_X86_64=$($env:GSTREAMER_1_0_ROOT_MINGW_X86_64)" | |
| exit 1 | |
| } | |
| $gstBin = Join-Path $gstRoot "bin" | |
| if (-not (Test-Path $gstBin)) { | |
| Write-Error "GStreamer bin not found at: $gstBin" | |
| exit 1 | |
| } | |
| # Copy GStreamer DLLs | |
| Copy-Item "$gstBin\*.dll" -Destination dist/ -Force | |
| # Ensure critical GStreamer/GLib runtime DLLs are present | |
| $requiredDlls = @( | |
| "libgstreamer-1.0-0.dll", | |
| "libgstbase-1.0-0.dll", | |
| "libgstaudio-1.0-0.dll", | |
| "libgstvideo-1.0-0.dll", | |
| "libglib-2.0-0.dll", | |
| "libgobject-2.0-0.dll", | |
| "libgio-2.0-0.dll" | |
| ) | |
| foreach ($dll in $requiredDlls) { | |
| if (Test-Path "$gstBin\$dll") { | |
| Copy-Item "$gstBin\$dll" -Destination dist/ -Force | |
| } | |
| } | |
| $missing = @() | |
| foreach ($dll in $requiredDlls) { | |
| if (-not (Test-Path "dist\$dll")) { $missing += $dll } | |
| } | |
| if ($missing.Count -gt 0) { | |
| Write-Error "Missing required GStreamer DLLs in dist: $($missing -join ', ')" | |
| Get-ChildItem dist | Select-Object Name | Out-String | Write-Host | |
| exit 1 | |
| } | |
| # Copy Plugins | |
| $gstPlugins = Join-Path $gstRoot "lib\gstreamer-1.0" | |
| if (Test-Path $gstPlugins) { | |
| New-Item -ItemType Directory -Path dist/lib/gstreamer-1.0 -Force | |
| Copy-Item "$gstPlugins\*.dll" -Destination dist/lib/gstreamer-1.0 -Force | |
| } else { | |
| New-Item -ItemType Directory -Path dist/lib/gstreamer-1.0 -Force | |
| } | |
| # Copy Scanner (Check libexec) | |
| $gstLibexec = Join-Path $gstRoot "libexec\gstreamer-1.0" | |
| $scannerCandidates = @( | |
| "$gstLibexec\gst-plugin-scanner.exe", | |
| "$gstRoot\bin\gst-plugin-scanner.exe", | |
| "$gstRoot\lib\gstreamer-1.0\gst-plugin-scanner.exe" | |
| ) | |
| $scannerCopied = $false | |
| foreach ($scanner in $scannerCandidates) { | |
| if (Test-Path $scanner) { | |
| Copy-Item $scanner -Destination dist/lib/gstreamer-1.0 -Force | |
| $scannerCopied = $true | |
| break | |
| } | |
| } | |
| if (-not $scannerCopied) { | |
| Write-Warning "gst-plugin-scanner.exe not found in GStreamer root" | |
| } | |
| # Copy OpenIPC QML module (Custom module) | |
| if (Test-Path build/OpenIPC) { | |
| Copy-Item build/OpenIPC -Destination dist/ -Recurse -Force | |
| } | |
| # Run windeployqt to deploy standard Qt dependencies (including Qt.labs.folderlistmodel) | |
| # We run this on the final dist folder to ensure everything is self-contained | |
| $windeployqt = (Get-Command windeployqt.exe -ErrorAction SilentlyContinue).Source | |
| if (-not $windeployqt) { $windeployqt = (Get-Command windeployqt -ErrorAction SilentlyContinue).Source } | |
| if (-not $windeployqt) { Write-Error "windeployqt not found in PATH"; exit 1 } | |
| & $windeployqt --qmldir src/ui --dir dist dist/appOpenIPC-Dashboard.exe | |
| - name: Install Inno Setup | |
| run: choco install innosetup | |
| - name: Update Installer Version | |
| shell: pwsh | |
| run: | | |
| $version = "${{ github.ref_name }}".TrimStart('v') | |
| (Get-Content setup.iss) -replace '#define MyAppVersion ".*"', "#define MyAppVersion ""$version""" | Set-Content setup.iss | |
| - name: Build Installer | |
| run: iscc setup.iss | |
| - name: Upload Installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-installer | |
| path: OpenIPC-Dashboard-Installer.exe | |
| build-linux: | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential libgl1-mesa-dev pkg-config libfuse2 imagemagick patchelf libwayland-dev libunwind-dev squashfs-tools desktop-file-utils \ | |
| libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ | |
| gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ | |
| gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl | |
| - name: Install Qt | |
| uses: jurplel/install-qt-action@v3 | |
| with: | |
| version: '6.4.2' | |
| host: 'linux' | |
| target: 'desktop' | |
| modules: 'qtmultimedia' | |
| - name: Install libxcb dependencies | |
| run: sudo apt-get install -y libxcb-cursor0 | |
| - name: Configure CMake | |
| run: cmake -B build -DCMAKE_BUILD_TYPE=Release | |
| - name: Build | |
| run: cmake --build build --config Release | |
| - name: Upload CMake logs (Linux) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-cmake-logs | |
| path: | | |
| build/CMakeFiles/CMakeOutput.log | |
| build/CMakeFiles/CMakeError.log | |
| build/CMakeCache.txt | |
| build/compile_commands.json | |
| - name: Helper - Create Desktop File | |
| run: | | |
| echo "[Desktop Entry]" > appOpenIPC-Dashboard.desktop | |
| echo "Type=Application" >> appOpenIPC-Dashboard.desktop | |
| echo "Name=OpenIPC Dashboard" >> appOpenIPC-Dashboard.desktop | |
| echo "Exec=appOpenIPC-Dashboard" >> appOpenIPC-Dashboard.desktop | |
| echo "Icon=openipc-dashboard" >> appOpenIPC-Dashboard.desktop | |
| echo "Categories=Utility;" >> appOpenIPC-Dashboard.desktop | |
| - name: Create AppImage | |
| env: | |
| VERSION: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| exec > >(tee -a create_appimage.log) 2>&1 | |
| set -x | |
| : > linuxdeployqt.log | |
| : > appimagetool.log | |
| # Download linuxdeployqt | |
| wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage | |
| chmod +x linuxdeployqt-continuous-x86_64.AppImage | |
| ./linuxdeployqt-continuous-x86_64.AppImage --appimage-extract | |
| rm -rf linuxdeployqt-root | |
| mv squashfs-root linuxdeployqt-root | |
| # Prepare AppDir | |
| mkdir -p AppDir/usr/bin | |
| cp build/appOpenIPC-Dashboard AppDir/usr/bin/ | |
| chmod +x AppDir/usr/bin/appOpenIPC-Dashboard | |
| # Prepare Icon (Use existing PNG and resize to ensure 256x256) | |
| mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps/ | |
| convert resources/appicon.png -resize 256x256 AppDir/usr/share/icons/hicolor/256x256/apps/openipc-dashboard.png | |
| cp AppDir/usr/share/icons/hicolor/256x256/apps/openipc-dashboard.png AppDir/openipc-dashboard.png | |
| # Copy desktop file | |
| mkdir -p AppDir/usr/share/applications/ | |
| cp appOpenIPC-Dashboard.desktop AppDir/usr/share/applications/ | |
| cp appOpenIPC-Dashboard.desktop AppDir/appOpenIPC-Dashboard.desktop | |
| # Copy custom libs to standard location expected by AppImage (usr/lib) | |
| mkdir -p AppDir/usr/lib | |
| cp libs/dahua/lib/Linux64/*.so AppDir/usr/lib/ | |
| cp libs/onnxruntime/lib/Linux64/*.so* AppDir/usr/lib/ | |
| # Copy OpenIPC assets (QML modules, models) - mirror Windows behavior | |
| if [ -d "build/OpenIPC" ]; then | |
| cp -r build/OpenIPC AppDir/usr/bin/ | |
| fi | |
| # Copy GStreamer plugins (minimal set to avoid missing dependency warnings) | |
| mkdir -p AppDir/usr/lib/gstreamer-1.0 | |
| GST_PLUGIN_DIR="/usr/lib/x86_64-linux-gnu/gstreamer-1.0" | |
| GST_PLUGINS=( | |
| libgstcoreelements.so | |
| libgstplayback.so | |
| libgsttypefindfunctions.so | |
| libgstapp.so | |
| libgstaudioconvert.so | |
| libgstaudioresample.so | |
| libgstaudioparsers.so | |
| libgstaacparse.so | |
| libgstfaad.so | |
| libgstvolume.so | |
| libgstautodetect.so | |
| libgstvideoconvert.so | |
| libgstvideoscale.so | |
| libgstvideorate.so | |
| libgstvideoparsers.so | |
| libgstvideoparsersbad.so | |
| libgstvideofilters.so | |
| libgstvideofilter.so | |
| libgstvideofiltersbad.so | |
| libgstdeinterlace.so | |
| libgstrtp.so | |
| libgstrtpmanager.so | |
| libgstrtsp.so | |
| libgstudp.so | |
| libgsttcp.so | |
| libgstisomp4.so | |
| libgstmatroska.so | |
| libgstmpegtsdemux.so | |
| libgstde265.so | |
| libgstlibav.so | |
| libgstopenh264.so | |
| libgstalsa.so | |
| libgstpulseaudio.so | |
| ) | |
| for plugin in "${GST_PLUGINS[@]}"; do | |
| if [ -f "$GST_PLUGIN_DIR/$plugin" ]; then | |
| cp "$GST_PLUGIN_DIR/$plugin" AppDir/usr/lib/gstreamer-1.0/ | |
| fi | |
| done | |
| # Copy plugin scanner | |
| if [ -f "/usr/lib/x86_64-linux-gnu/gstreamer-1.0/gst-plugin-scanner" ]; then | |
| cp /usr/lib/x86_64-linux-gnu/gstreamer-1.0/gst-plugin-scanner AppDir/usr/lib/gstreamer-1.0/ | |
| elif [ -f "/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner" ]; then | |
| cp /usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner AppDir/usr/lib/gstreamer-1.0/ | |
| fi | |
| # Copy GStreamer core libraries to avoid host/AppImage version mismatch | |
| cp /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstaudio-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstvideo-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstapp-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstpbutils-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstrtp-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstrtsp-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgsttag-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libgstnet-1.0.so.* AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/liborc-0.4.so.* AppDir/usr/lib/ | |
| # Copy GStreamer bad/libav runtime libs needed for AAC/H265 and MP4 playback | |
| for lib in \ | |
| /usr/lib/x86_64-linux-gnu/libgstcodecparsers-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstcodecs-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstbadaudio-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstadaptivedemux-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstwebrtc-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstsctp-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstwayland-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstisoff-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgstmpegts-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libgsturidownloader-1.0.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libpocketsphinx.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libopenh264.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libavcodec.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libavformat.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libavutil.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libswresample.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libswscale.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libavfilter.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libpostproc.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libfaad.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libde265.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libx265.so.* \ | |
| /usr/lib/x86_64-linux-gnu/libx264.so.*; do | |
| if [ -e "$lib" ]; then | |
| cp $lib AppDir/usr/lib/ | |
| fi | |
| done | |
| # Create AppRun wrapper to force bundled GStreamer | |
| printf '%s\n' \ | |
| '#!/bin/sh' \ | |
| 'HERE="$(dirname "$(readlink -f "$0")")"' \ | |
| 'export LD_LIBRARY_PATH="$HERE/usr/lib:$LD_LIBRARY_PATH"' \ | |
| 'export GST_PLUGIN_PATH="$HERE/usr/lib/gstreamer-1.0"' \ | |
| 'export GST_PLUGIN_SCANNER="$HERE/usr/lib/gstreamer-1.0/gst-plugin-scanner"' \ | |
| 'export GST_PLUGIN_SYSTEM_PATH_1_0=""' \ | |
| 'exec "$HERE/usr/bin/appOpenIPC-Dashboard" "$@"' \ | |
| > AppDir/AppRun | |
| chmod +x AppDir/AppRun | |
| # Fix OpenSSL: Copy system OpenSSL 1.1 libs (standard on Ubuntu 20.04) | |
| # We need to provide libssl1.1 because Qt 6.4 binaries are linked against it. | |
| cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 AppDir/usr/lib/ | |
| cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 AppDir/usr/lib/ | |
| # Patch RPATH of the main binary to find libs in ../lib relative to itself | |
| # We DO NOT set LD_LIBRARY_PATH so we can verify if RPATH is working correctly | |
| patchelf --set-rpath '$ORIGIN/../lib' AppDir/usr/bin/appOpenIPC-Dashboard | |
| # Debug: check ldd after patching | |
| echo "LDD after patching (Expect libs in .../AppDir/usr/lib):" | |
| ldd AppDir/usr/bin/appOpenIPC-Dashboard | |
| # HACK: Fix silent exit 1 loop in linuxdeployqt when using -unsupported-allow-new-glibc | |
| # The tool checks for this specific file if the flag is present, otherwise it exits 1 silently | |
| mkdir -p AppDir/usr/share/doc/libc6 | |
| touch AppDir/usr/share/doc/libc6/copyright | |
| # Run linuxdeployqt (deploy + appimage) | |
| # We use -no-strip to avoid potential issues with stripping failing | |
| # We use -bundle-non-qt-libs so it attempts to include FUSE or other sys libs if needed | |
| # UNSET QT_PLUGIN_PATH | |
| unset QT_PLUGIN_PATH | |
| # Explicitly set QMAKE path (linuxdeployqt uses it to find plugins) | |
| export QMAKE="$(command -v qmake)" | |
| # Use -verbose=0 (less output) to see if we can spot the actual error message at the end | |
| # Sometimes verbose output hides the real error or crashes the logger | |
| set +e | |
| ./linuxdeployqt-root/AppRun AppDir/usr/share/applications/appOpenIPC-Dashboard.desktop \ | |
| -qmldir=src \ | |
| -qmldir=build/OpenIPC \ | |
| -exclude-libs=libglib-2.0.so.0,libgthread-2.0.so.0,libgobject-2.0.so.0,libgmodule-2.0.so.0,libgio-2.0.so.0 \ | |
| -extra-plugins=iconengines,imageformats,platforms,platformthemes,wayland-decoration-client,wayland-graphics-integration-client,wayland-shell-integration \ | |
| -verbose=2 \ | |
| -no-strip \ | |
| -no-translations \ | |
| -qmake=$QMAKE 2>&1 | tee linuxdeployqt.log | |
| LINUXDEPLOYQT_RC=${PIPESTATUS[0]} | |
| set -e | |
| if [ $LINUXDEPLOYQT_RC -ne 0 ]; then | |
| echo "linuxdeployqt failed with exit code $LINUXDEPLOYQT_RC" | |
| exit $LINUXDEPLOYQT_RC | |
| fi | |
| # Pack AppImage explicitly (use appimagetool bundled with linuxdeployqt) | |
| if [ ! -f ./linuxdeployqt-root/usr/bin/appimagetool ]; then | |
| echo "appimagetool not found in linuxdeployqt-root" | |
| ls -la linuxdeployqt-root/usr/bin || true | |
| exit 1 | |
| fi | |
| chmod +x ./linuxdeployqt-root/usr/bin/appimagetool | |
| echo "Using appimagetool: ./linuxdeployqt-root/usr/bin/appimagetool" | |
| set +e | |
| ARCH=x86_64 VERSION=${VERSION} ./linuxdeployqt-root/usr/bin/appimagetool -v AppDir OpenIPC-Dashboard-Linux.AppImage 2>&1 | tee appimagetool.log | |
| APPIMAGETOOL_RC=${PIPESTATUS[0]} | |
| set -e | |
| if [ $APPIMAGETOOL_RC -ne 0 ]; then | |
| echo "appimagetool failed with exit code $APPIMAGETOOL_RC" | |
| exit $APPIMAGETOOL_RC | |
| fi | |
| if [ ! -f OpenIPC-Dashboard-Linux.AppImage ]; then | |
| echo "AppImage not created" | |
| find . -maxdepth 2 -name "*.AppImage" -type f -ls || true | |
| ls -la AppDir || true | |
| exit 1 | |
| fi | |
| ls -la *.AppImage | |
| mkdir -p tools | |
| mv linuxdeployqt-continuous-x86_64.AppImage tools/ | |
| - name: Upload AppImage logs (Linux) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-appimage-logs | |
| path: | | |
| appimagetool.log | |
| linuxdeployqt.log | |
| create_appimage.log | |
| - name: Rename AppImage | |
| run: | | |
| if [ -f OpenIPC-Dashboard-Linux.AppImage ]; then | |
| echo "AppImage already built" | |
| exit 0 | |
| fi | |
| FOUND=$(find . -maxdepth 3 -name "*.AppImage" -type f ! -name "linuxdeployqt-continuous-x86_64.AppImage" | head -n 1) | |
| if [ -n "$FOUND" ]; then | |
| mv "$FOUND" OpenIPC-Dashboard-Linux.AppImage | |
| echo "Moved AppImage: $FOUND" | |
| else | |
| echo "OpenIPC-Dashboard-Linux.AppImage not found" | |
| ls -la | |
| find . -maxdepth 3 -name "*.AppImage" -type f -ls || true | |
| exit 1 | |
| fi | |
| - name: Upload AppImage | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-appimage | |
| path: OpenIPC-Dashboard-Linux.AppImage | |
| release: | |
| needs: [build-windows, build-linux] | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - name: Download Windows Installer | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-installer | |
| - name: Download Linux AppImage | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: linux-appimage | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| OpenIPC-Dashboard-Installer.exe | |
| OpenIPC-Dashboard-Linux.AppImage |