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"