From 4e56297b22dc3ec893e75cccee4ec48189907d98 Mon Sep 17 00:00:00 2001 From: hightemp Date: Tue, 27 Oct 2026 14:57:36 +0300 Subject: [PATCH] build(desktop): check the system libraries and say how to install them make desktop-build and desktop-vet first run scripts/desktop-deps.sh: a C compiler for cgo and, on Linux, the pkg-config modules of Wails (GTK4 and WebKitGTK 6.0, or GTK3 and WebKit2GTK 4.1). For what is missing it prints the install command of the distribution (apt, dnf, pacman, zypper, apk), suggests GTK3 where WebKitGTK 6.0 does not exist (Ubuntu 22.04, Debian 12), and the compiler on macOS and Windows. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 2 +- Makefile | 14 +++- README.md | 2 +- README.ru.md | 2 +- desktop/README.md | 3 +- scripts/desktop-deps.sh | 158 +++++++++++++++++++++++++++++++++++ scripts/desktop-deps_test.sh | 92 ++++++++++++++++++++ 7 files changed, 266 insertions(+), 7 deletions(-) create mode 100755 scripts/desktop-deps.sh create mode 100755 scripts/desktop-deps_test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6892e1..df02c1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: - run: make ui-deps - run: make build # The installer from the README one-liner, against a fake GitHub Releases server - - run: make scripts-lint install-test changed-areas-test release-check-test + - run: make scripts-lint install-test changed-areas-test release-check-test desktop-deps-test e2e: name: Browser tests (Playwright) diff --git a/Makefile b/Makefile index ae67bef..2eb6d45 100644 --- a/Makefile +++ b/Makefile @@ -199,8 +199,12 @@ LINUX_GTK ?= gtk4 DESKTOP_TAGS := desktop embedui $(if $(filter gtk3,$(LINUX_GTK)),gtk3) DESKTOP_BIN := $(DIST)/wapp-debugger-desktop$(EXE) +.PHONY: desktop-deps +desktop-deps: ## Check the C compiler and, on Linux, GTK and WebKitGTK; prints how to install what is missing + scripts/desktop-deps.sh $(LINUX_GTK) + .PHONY: desktop-build -desktop-build: ui-build ## Build the desktop app for the current OS (requires CGO) +desktop-build: desktop-deps ui-build ## Build the desktop app for the current OS (requires CGO) cd server && CGO_ENABLED=1 go build -tags "$(DESKTOP_TAGS)" -trimpath -ldflags "$(LDFLAGS)" -o $(DESKTOP_BIN) ./cmd/wapp-debugger-desktop .PHONY: icons @@ -208,7 +212,7 @@ icons: ## Render desktop/icons/icon.svg into the PNG, ICO and ICNS icons (needs node scripts/icons.mjs .PHONY: desktop-vet -desktop-vet: ui-build ## go vet the desktop code (with its build tags; the embedded UI must be built) +desktop-vet: desktop-deps ui-build ## go vet the desktop code (with its build tags; the embedded UI must be built) cd server && CGO_ENABLED=1 go vet -tags "$(DESKTOP_TAGS)" ./cmd/wapp-debugger-desktop .PHONY: desktop-selftest @@ -316,6 +320,10 @@ changed-areas-test: ## Test scripts/changed-areas.sh, which CI uses to skip jobs release-check-test: ## Test scripts/release-check.sh: no stable release with a pre-release library scripts/release-check_test.sh +.PHONY: desktop-deps-test +desktop-deps-test: ## Test scripts/desktop-deps.sh against fake pkg-config and compilers + scripts/desktop-deps_test.sh + .PHONY: ssh-e2e ssh-e2e: ## The viewer against a real OpenSSH server in Docker (throwaway key, no ssh-agent) scripts/ssh-e2e.sh @@ -323,7 +331,7 @@ ssh-e2e: ## The viewer against a real OpenSSH server in Docker (throwaway key, n .PHONY: scripts-lint scripts-lint: ## shellcheck the shell scripts (shellcheck binary, or its Docker image) @if command -v shellcheck >/dev/null 2>&1; then shellcheck scripts/*.sh; \ - else docker run --rm -v "$(ROOT)/scripts:/s:ro" koalaman/shellcheck:v0.10.0 /s/install.sh /s/install_test.sh /s/ssh-e2e.sh /s/desktop-package.sh /s/changed-areas.sh /s/changed-areas_test.sh /s/release-check.sh /s/release-check_test.sh; fi + else docker run --rm -v "$(ROOT)/scripts:/s:ro" koalaman/shellcheck:v0.10.0 /s/install.sh /s/install_test.sh /s/ssh-e2e.sh /s/desktop-package.sh /s/changed-areas.sh /s/changed-areas_test.sh /s/release-check.sh /s/release-check_test.sh /s/desktop-deps.sh /s/desktop-deps_test.sh; fi .PHONY: workflows-lint workflows-lint: $(ACTIONLINT) ## Lint GitHub Actions workflows diff --git a/README.md b/README.md index 7f19836..88fabb0 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ What the desktop adds to the browser version (**Settings** in the header): - start at login; the window comes back where it was; - a notice in the header when a newer version is out (GitHub Releases once a day; can be switched off). -From source: `make desktop-build` (needs GTK4 + WebKitGTK 6.0 on Linux), then `dist/wapp-debugger-desktop [--logs-dir …]`; `--logs-dir` is optional here too. `make desktop-package` builds the installers of the current system; more in [desktop/README.md](desktop/README.md). +From source: `make desktop-build` (needs a C compiler and, on Linux, GTK4 + WebKitGTK 6.0; `make desktop-deps` checks them and prints how to install what is missing on your system), then `dist/wapp-debugger-desktop [--logs-dir …]`; `--logs-dir` is optional here too. `make desktop-package` builds the installers of the current system; more in [desktop/README.md](desktop/README.md). ## Docker diff --git a/README.ru.md b/README.ru.md index 7dbaf68..bdf9f04 100644 --- a/README.ru.md +++ b/README.ru.md @@ -157,7 +157,7 @@ wapp-debugger serve --logs-dir /var/www/app/tmp/logs # откройте http:/ - запуск при входе в систему; окно открывается там, где было; - сообщение в шапке о новой версии (GitHub Releases раз в сутки; отключается). -Из исходников: `make desktop-build` (в Linux нужны GTK4 + WebKitGTK 6.0), затем `dist/wapp-debugger-desktop [--logs-dir …]`; `--logs-dir` и здесь необязателен. `make desktop-package` собирает установщики текущей системы; подробнее — в [desktop/README.md](desktop/README.md). +Из исходников: `make desktop-build` (нужен компилятор C, а в Linux ещё GTK4 + WebKitGTK 6.0; `make desktop-deps` проверяет их и подсказывает, как поставить недостающее в вашей системе), затем `dist/wapp-debugger-desktop [--logs-dir …]`; `--logs-dir` и здесь необязателен. `make desktop-package` собирает установщики текущей системы; подробнее — в [desktop/README.md](desktop/README.md). ## Docker diff --git a/desktop/README.md b/desktop/README.md index 33b7516..e69170c 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -3,7 +3,8 @@ The desktop viewer is built with Wails v3 on top of `server/internal/*` and the same UI (`ui/`). Its entry point lives in `server/cmd/wapp-debugger-desktop` (build tag `desktop`). ```sh -make desktop-build # build for the current OS (requires CGO) +make desktop-deps # check the C compiler, GTK and WebKitGTK; prints how to install what is missing +make desktop-build # build for the current OS (requires CGO; runs desktop-deps first) make desktop-build LINUX_GTK=gtk3 # Linux with GTK3 + WebKit2GTK 4.1 instead of GTK4 make desktop-selftest # open a window and check REST and the event stream inside Wails make desktop-dev LOGS_DIR=/path/to/project/tmp/logs diff --git a/scripts/desktop-deps.sh b/scripts/desktop-deps.sh new file mode 100755 index 0000000..7631c1b --- /dev/null +++ b/scripts/desktop-deps.sh @@ -0,0 +1,158 @@ +#!/bin/bash +# Checks what the desktop build needs from the system (a C compiler for cgo; GTK and WebKitGTK on +# Linux) and, when something is missing, prints how to install it on this system. +# Usage: desktop-deps.sh [gtk4|gtk3]. Exit 0: all there, 1: something is missing. +# Tests set DESKTOP_DEPS_OS (uname -s) and DESKTOP_DEPS_OS_RELEASE (/etc/os-release); +# PKG_CONFIG and CC are the ones cgo uses. +set -euo pipefail + +gtk=${1:-gtk4} +os=${DESKTOP_DEPS_OS:-$(uname -s)} +os_release=${DESKTOP_DEPS_OS_RELEASE:-/etc/os-release} +pkg_config=${PKG_CONFIG:-pkg-config} +compiler=${CC:-$(go env CC 2>/dev/null || echo cc)} +compiler=${compiler%% *} + +have() { command -v "$1" >/dev/null 2>&1; } + +if ! have "$compiler"; then + case $os in + Darwin) + echo "desktop-deps: no C compiler ($compiler) for cgo; install the Xcode command line tools:" + echo " xcode-select --install" + exit 1 + ;; + MINGW* | MSYS* | CYGWIN* | Windows_NT) + echo "desktop-deps: no C compiler ($compiler) for cgo; install MinGW-w64 GCC, for example from MSYS2" + echo "(https://www.msys2.org/), and put its bin directory on PATH:" + echo " pacman -S mingw-w64-ucrt-x86_64-gcc" + exit 1 + ;; + esac +fi +case $os in Linux) ;; *) exit 0 ;; esac + +# The pkg-config modules of Wails v3 (#cgo lines of github.com/wailsapp/wails/v3) +if [ "$gtk" = gtk3 ]; then + modules="gtk+-3.0 webkit2gtk-4.1 gdk-3.0 libsoup-3.0 gio-unix-2.0 glib-2.0 gobject-2.0 x11" +else + modules="gtk4 webkitgtk-6.0 libsoup-3.0 gio-unix-2.0 glib-2.0 gobject-2.0 x11" +fi + +tools=() +have "$compiler" || tools+=(compiler) +missing=() +if have "$pkg_config"; then + for module in $modules; do + "$pkg_config" --exists "$module" || missing+=("$module") + done +else + # Without pkg-config nothing can be checked: the libraries go to the install command as well + tools+=(pkg-config) + read -ra missing <<<"$modules" +fi +[ ${#tools[@]} -eq 0 ] && [ ${#missing[@]} -eq 0 ] && exit 0 + +id='' id_like='' version_id='' pretty_name='' +if [ -r "$os_release" ]; then + # shellcheck disable=SC1090 + { + read -r id + read -r id_like + read -r version_id + read -r pretty_name + } < <(. "$os_release" && printf '%s\n' "${ID:-}" "${ID_LIKE:-}" "${VERSION_ID:-}" "${PRETTY_NAME:-}") +fi +family='' +for candidate in $id $id_like; do + case $candidate in + debian | ubuntu) family=debian ;; + fedora | rhel | centos) family=fedora ;; + arch) family=arch ;; + opensuse* | suse | sles) family=suse ;; + alpine) family=alpine ;; + esac + [ -n "$family" ] && break +done + +# The package that provides a pkg-config module, a compiler or pkg-config itself +package() { + case $family:$1 in + debian:compiler) echo build-essential ;; + debian:pkg-config) echo pkg-config ;; + debian:gtk4) echo libgtk-4-dev ;; + debian:webkitgtk-6.0) echo libwebkitgtk-6.0-dev ;; + debian:gtk+-3.0 | debian:gdk-3.0) echo libgtk-3-dev ;; + debian:webkit2gtk-4.1) echo libwebkit2gtk-4.1-dev ;; + debian:libsoup-3.0) echo libsoup-3.0-dev ;; + debian:gio-unix-2.0 | debian:glib-2.0 | debian:gobject-2.0) echo libglib2.0-dev ;; + debian:x11) echo libx11-dev ;; + arch:compiler) echo base-devel ;; + arch:pkg-config) echo pkgconf ;; + arch:gtk4) echo gtk4 ;; + arch:webkitgtk-6.0) echo webkitgtk-6.0 ;; + arch:gtk+-3.0 | arch:gdk-3.0) echo gtk3 ;; + arch:webkit2gtk-4.1) echo webkit2gtk-4.1 ;; + arch:libsoup-3.0) echo libsoup3 ;; + arch:gio-unix-2.0 | arch:glib-2.0 | arch:gobject-2.0) echo glib2 ;; + arch:x11) echo libx11 ;; + alpine:compiler) echo build-base ;; + alpine:pkg-config) echo pkgconf ;; + alpine:gtk4) echo gtk4.0-dev ;; + alpine:webkitgtk-6.0) echo webkit2gtk-6.0-dev ;; + alpine:gtk+-3.0 | alpine:gdk-3.0) echo gtk+3.0-dev ;; + alpine:webkit2gtk-4.1) echo webkit2gtk-4.1-dev ;; + alpine:libsoup-3.0) echo libsoup3-dev ;; + alpine:gio-unix-2.0 | alpine:glib-2.0 | alpine:gobject-2.0) echo glib-dev ;; + alpine:x11) echo libx11-dev ;; + # dnf and zypper install a package by the pkg-config module it provides + fedora:compiler | suse:compiler) echo gcc ;; + fedora:pkg-config) echo pkgconf-pkg-config ;; + suse:pkg-config) echo pkg-config ;; + *) echo "'pkgconfig($1)'" ;; + esac +} +packages() { + local item name list=() + for item in "$@"; do + name=$(package "$item") + [[ " ${list[*]} " == *" $name "* ]] || list+=("$name") + done + echo "${list[*]}" +} +install_command() { + case $family in + debian) echo "sudo apt-get install -y $(packages "$@")" ;; + fedora) echo "sudo dnf install $(packages "$@")" ;; + arch) echo "sudo pacman -S --needed $(packages "$@")" ;; + suse) echo "sudo zypper install $(packages "$@")" ;; + alpine) echo "sudo apk add $(packages "$@")" ;; + esac +} + +echo "desktop-deps: the desktop build ($gtk) needs what this system does not have:" +[ ${#tools[@]} -gt 0 ] && echo " tools: ${tools[*]/compiler/a C compiler ($compiler)}" +[ ${#missing[@]} -gt 0 ] && echo " pkg-config modules: ${missing[*]}" +echo + +old_release=false +if [ "$gtk" = gtk4 ] && [ -n "$version_id" ]; then + case $id in + ubuntu) [ "$(printf '%s\n' "$version_id" 24.04 | sort -V | head -1)" != 24.04 ] && old_release=true ;; + debian) [ "$(printf '%s\n' "$version_id" 13 | sort -V | head -1)" != 13 ] && old_release=true ;; + esac +fi + +if [ "$old_release" = true ]; then + echo "${pretty_name:-This release} has no WebKitGTK 6.0 (it comes with Ubuntu 24.04 and Debian 13)." + echo "Build with GTK3 and WebKit2GTK 4.1 instead:" + echo " $(install_command "${tools[@]}" gtk+-3.0 webkit2gtk-4.1 libsoup-3.0 glib-2.0 x11)" + echo " make desktop-build LINUX_GTK=gtk3" +elif [ -n "$family" ]; then + echo "Install them (${pretty_name:-$id}):" + echo " $(install_command "${tools[@]}" "${missing[@]}")" +else + echo "Install the development packages that provide these pkg-config modules" + echo "(${pretty_name:-an unknown distribution}); their names differ between distributions." +fi +exit 1 diff --git a/scripts/desktop-deps_test.sh b/scripts/desktop-deps_test.sh new file mode 100755 index 0000000..2512633 --- /dev/null +++ b/scripts/desktop-deps_test.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# Tests scripts/desktop-deps.sh against fake pkg-config and compilers: make desktop-deps-test +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +# pkg-config that knows only the modules in $FAKE_PC_MODULES +cat > "$WORK/pkg-config" <<'EOF' +#!/bin/bash +[ "$1" = --exists ] && shift +for module in "$@"; do + [[ " $FAKE_PC_MODULES " == *" $module "* ]] || exit 1 +done +EOF +printf '#!/bin/sh\n' > "$WORK/cc" +chmod +x "$WORK/pkg-config" "$WORK/cc" + +GTK4="gtk4 webkitgtk-6.0 libsoup-3.0 gio-unix-2.0 glib-2.0 gobject-2.0 x11" +GTK3="gtk+-3.0 webkit2gtk-4.1 gdk-3.0 libsoup-3.0 gio-unix-2.0 glib-2.0 gobject-2.0 x11" + +failures=0 +indent() { while IFS= read -r line; do echo " $line"; done <<<"$1"; } +release() { printf 'ID=%s\nID_LIKE="%s"\nVERSION_ID="%s"\nPRETTY_NAME="%s"\n' "$1" "$2" "$3" "$4" > "$WORK/os-release"; } +# expect NAME WANT(ok|missing) OS GTK MODULES [TEXT…]: the exit code and every TEXT in the output +expect() { + local name=$1 want=$2 os=$3 gtk=$4 modules=$5 got=0 out text + shift 5 + out=$(DESKTOP_DEPS_OS=$os DESKTOP_DEPS_OS_RELEASE="$WORK/os-release" FAKE_PC_MODULES=$modules \ + PKG_CONFIG=${FAKE_PKG_CONFIG:-$WORK/pkg-config} CC=${FAKE_CC:-$WORK/cc} \ + "$ROOT/scripts/desktop-deps.sh" "$gtk" 2>&1) || got=$? + if { [ "$want" = ok ] && [ "$got" -ne 0 ]; } || { [ "$want" = missing ] && [ "$got" -eq 0 ]; }; then + echo "FAIL $name: want $want, exit $got" + indent "$out" + failures=$((failures + 1)) + return + fi + for text in "$@"; do + if ! grep -qF -- "$text" <<<"$out"; then + echo "FAIL $name: no \"$text\" in" + indent "$out" + failures=$((failures + 1)) + fi + done +} + +release ubuntu debian 24.04 "Ubuntu 24.04.5 LTS" +expect "everything installed" ok Linux gtk4 "$GTK4" +expect "GTK3 installed" ok Linux gtk3 "$GTK3" +expect "Ubuntu without GTK4 and WebKitGTK" missing Linux gtk4 "glib-2.0 gobject-2.0 gio-unix-2.0 libsoup-3.0 x11" \ + "gtk4 webkitgtk-6.0" "Ubuntu 24.04.5 LTS" "sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev" +expect "Ubuntu with GTK3" missing Linux gtk3 "glib-2.0 gobject-2.0 gio-unix-2.0 x11" \ + "sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libsoup-3.0-dev" +FAKE_PKG_CONFIG="$WORK/no-pkg-config" expect "no pkg-config" missing Linux gtk4 "" \ + "pkg-config" "sudo apt-get install -y pkg-config" +FAKE_CC="$WORK/no-cc" expect "no C compiler" missing Linux gtk4 "$GTK4" "build-essential" + +release ubuntu debian 22.04 "Ubuntu 22.04.4 LTS" +expect "Ubuntu 22.04 has no WebKitGTK 6.0" missing Linux gtk4 "" "LINUX_GTK=gtk3" + +release debian "" 13 "Debian GNU/Linux 13 (trixie)" +expect "Debian" missing Linux gtk4 "" "sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev" + +release debian "" "" "Debian GNU/Linux forky/sid" +expect "Debian testing has no version" missing Linux gtk4 "" "sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev" + +release fedora "" 42 "Fedora Linux 42" +expect "Fedora" missing Linux gtk4 "" "sudo dnf install 'pkgconfig(gtk4)' 'pkgconfig(webkitgtk-6.0)'" + +release linuxmint "ubuntu debian" 22 "Linux Mint 22" +expect "a derivative of Ubuntu" missing Linux gtk4 "" "sudo apt-get install -y libgtk-4-dev" + +release arch "" "" "Arch Linux" +expect "Arch" missing Linux gtk4 "" "sudo pacman -S --needed gtk4 webkitgtk-6.0" + +release opensuse-tumbleweed "opensuse suse" 20260101 "openSUSE Tumbleweed" +expect "openSUSE" missing Linux gtk4 "" "sudo zypper install 'pkgconfig(gtk4)'" + +release alpine "" 3.22 "Alpine Linux v3.22" +expect "Alpine" missing Linux gtk4 "" "sudo apk add gtk4.0-dev webkit2gtk-6.0-dev" + +release gentoo "" 2.17 "Gentoo Linux" +expect "an unknown distribution" missing Linux gtk4 "" "pkg-config modules" "gtk4 webkitgtk-6.0" + +expect "macOS with a compiler" ok Darwin gtk4 "" +FAKE_CC="$WORK/no-cc" expect "macOS without a compiler" missing Darwin gtk4 "" "xcode-select --install" +expect "Windows with a compiler" ok MINGW64_NT-10.0-26100 gtk4 "" +FAKE_CC="$WORK/no-cc" expect "Windows without a compiler" missing MINGW64_NT-10.0-26100 gtk4 "" "MSYS2" + +[ "$failures" -eq 0 ] && echo "desktop-deps: ok" +exit "$failures"