Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions .github/workflows/continuous.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,15 @@ jobs:
`Labrador-for-macOS.dmg` — signed + notarized universal build.
Needs macOS 10.13 High Sierra or newer on Intel, macOS 11 Big Sur
or newer on Apple Silicon. Drag Labrador into `/Applications`.
The command-line tool is inside the bundle:
`/Applications/Labrador.app/Contents/MacOS/labrador-cli`.

## Windows

`Labrador-for-Windows.exe` — installer bundling the app (64-bit and
32-bit; it installs the one matching your Windows), firmware, and
USB driver. Run it and accept the driver-install prompt at the end.
32-bit; it installs the one matching your Windows), the
`labrador-cli.exe` command-line tool beside it, firmware, and USB
driver. Run it and accept the driver-install prompt at the end.
Tested on Windows 10 and 11; Windows 7 SP1 and 8.1 should work (the
app has no newer OS dependencies) but are untested — reports
welcome.
Expand All @@ -94,16 +97,26 @@ jobs:
- `Labrador-for-Linux-x86_64.AppImage` /
`Labrador-for-Linux-arm64.AppImage` — `chmod +x` the file, then
run it. Installs the udev rules on first launch.
`./Labrador-for-Linux-<arch>.AppImage cli ...` runs the
command-line tool.
- `Labrador-for-Linux-x86_64.deb` / `Labrador-for-Linux-arm64.deb` —
`sudo dpkg -i <file>`.
`sudo dpkg -i <file>`. Installs `labrador` and `labrador-cli` on
`PATH`.

Needs a distro of Ubuntu 20.04 / Debian 11 / Pi OS bullseye vintage
or newer (glibc 2.31+).

## Android

`Labrador-for-Android.apk` — sideload on Android 7.1 "Nougat" or later
(API level 25).
(API level 25). No command-line tool on Android.

## Command-line tool

`labrador-cli` captures full-rate scope samples around a trigger to
CSV (`capture`) and flashes the bundled firmware (`update-firmware`)
without a window; see `Unified_App/cli/README.md` for the options
and examples.
EOF
)
# Recreate the release each run so stale assets never linger, mirroring
Expand Down
171 changes: 171 additions & 0 deletions .github/workflows/labrador-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Builds the headless labrador-cli (Unified_App/cli) on Linux, macOS and
# Windows and uploads each binary together with the bundled firmware it needs
# for `labrador-cli update-firmware`. The GUI workflows compile the same
# target as part of their default build; this one exists so the CLI has its
# own artifact and a fast signal when only cli/ or librador/ changed.
name: Build labrador-cli

on:
push:
branches: [unified-app]
paths:
- 'Unified_App/cli/**'
- 'Unified_App/librador/**'
- 'Unified_App/firmware/**'
- 'Unified_App/CMakeLists.txt'
- '.github/workflows/labrador-cli.yml'
pull_request:
branches: [unified-app]
paths:
- 'Unified_App/cli/**'
- 'Unified_App/librador/**'
- 'Unified_App/firmware/**'
- 'Unified_App/CMakeLists.txt'
- '.github/workflows/labrador-cli.yml'
workflow_dispatch:

jobs:
linux:
runs-on: ubuntu-22.04
defaults:
run:
working-directory: Unified_App
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
run: |
sudo apt-get -o Acquire::Retries=3 update
# SDL3 is configured as part of the tree (librador links it) and
# insists on the X11/Wayland dev packages once it finds X11.
sudo apt-get -o Acquire::Retries=3 install -y \
build-essential cmake ninja-build pkg-config libusb-1.0-0-dev \
libgl1-mesa-dev libegl1-mesa-dev \
libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev \
libxi-dev libxss-dev libxtst-dev libxkbcommon-dev \
libwayland-dev wayland-protocols libdbus-1-dev

- name: Build
run: |
cmake --preset linux
cmake --build --preset linux --target labrador_cli

- name: Smoke test (no board needed)
run: |
build/linux/cli/labrador-cli --help
build/linux/cli/labrador-cli help capture
build/linux/cli/labrador-cli help update-firmware
test -f build/linux/cli/assets/firmware/labrafirm_000C_03.hex

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: labrador-cli-linux-x86_64
path: |
Unified_App/build/linux/cli/labrador-cli
Unified_App/build/linux/cli/assets/firmware/*.hex

macos:
runs-on: macos-latest
defaults:
run:
working-directory: Unified_App
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Dependencies
run: brew install ninja pkg-config

# Same static universal libusb as the app build, so the binary runs on
# any Mac without a Homebrew libusb.
- name: Build universal libusb
run: |
set -euxo pipefail
curl -sSL -o libusb.tar.bz2 \
https://github.com/libusb/libusb/releases/download/v1.0.29/libusb-1.0.29.tar.bz2
tar xjf libusb.tar.bz2
cd libusb-1.0.29
./configure --prefix="$PWD/../libusb-univ" --enable-static --disable-shared \
CFLAGS="-arch arm64 -arch x86_64 -mmacosx-version-min=10.13" \
LDFLAGS="-arch arm64 -arch x86_64"
make -j$(sysctl -n hw.ncpu)
make install

- name: Build
run: |
export PKG_CONFIG_PATH="$PWD/libusb-univ/lib/pkgconfig"
cmake -S . -B build/macos -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLABRADOR_STATIC_LIBUSB=ON \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13
cmake --build build/macos --target labrador_cli -j$(sysctl -n hw.ncpu)

- name: Smoke test (no board needed)
run: |
lipo -info build/macos/cli/labrador-cli
build/macos/cli/labrador-cli --help
build/macos/cli/labrador-cli help capture
build/macos/cli/labrador-cli help update-firmware
test -f build/macos/cli/assets/firmware/labrafirm_000C_03.hex

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: labrador-cli-macos-universal
path: |
Unified_App/build/macos/cli/labrador-cli
Unified_App/build/macos/cli/assets/firmware/*.hex

windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
working-directory: Unified_App
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: false
install: >-
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-cmake
mingw-w64-x86_64-ninja
mingw-w64-x86_64-pkgconf
mingw-w64-x86_64-libusb

- name: Build
run: |
cmake --preset windows
cmake --build --preset windows --target labrador_cli

- name: Smoke test (no board needed)
run: |
./build/windows/cli/labrador-cli.exe --help
./build/windows/cli/labrador-cli.exe help capture
./build/windows/cli/labrador-cli.exe help update-firmware
test -f build/windows/cli/assets/firmware/labrafirm_000C_03.hex
# Static like the app: no MinGW or libusb DLLs beside the exe.
if objdump -x build/windows/cli/labrador-cli.exe | grep "DLL Name" | grep -iE "libusb|libstdc|libgcc|libwinpthread"; then
echo "labrador-cli.exe depends on non-system DLLs"; exit 1
fi

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: labrador-cli-windows-x86_64
path: |
Unified_App/build/windows/cli/labrador-cli.exe
Unified_App/build/windows/cli/assets/firmware/*.hex
94 changes: 76 additions & 18 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Build + package the Unified_App (SDL3/ImGui) Linux artifacts: AppImage and
# .deb for x86_64 and arm64 (Raspberry Pi), each carrying the firmware hex, udev
# rules, icon and .desktop entry. Also a Raspberry-Pi GLES2 compile check.
# .deb for x86_64 and arm64 (Raspberry Pi), each carrying the GUI, the headless
# labrador-cli, the firmware hex, udev rules, icon and .desktop entry. Also a
# Raspberry-Pi GLES2 compile check.
#
# Both package jobs build inside an ubuntu:20.04 container so the binaries'
# glibc floor is 2.31 (Ubuntu 20.04+ / Debian 11+ / Pi OS bullseye+). 20.04
Expand Down Expand Up @@ -119,17 +120,20 @@ jobs:
- name: Verify portability
run: |
set -euxo pipefail
BIN=build/appimage/labrador
# glibc floor must stay at the container's 2.31.
objdump -p "$BIN" | grep NEEDED
MAX=$(objdump -T "$BIN" | grep -oE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' | sort -Vu | tail -1)
echo "max glibc symbol: $MAX"
test "$(printf '%s\nGLIBC_2.31\n' "$MAX" | sort -V | tail -1)" = "GLIBC_2.31"
# C++ runtime and libusb are baked in, not dynamic dependencies.
if objdump -p "$BIN" | grep -E 'NEEDED.*(libstdc\+\+|libgcc_s|libusb)'; then
echo "ERROR: unexpected dynamic dependency" >&2
exit 1
fi
# The GUI and labrador-cli ship in the same AppImage, so both must
# meet the floors.
for BIN in build/appimage/labrador build/appimage/cli/labrador-cli; do
# glibc floor must stay at the container's 2.31.
objdump -p "$BIN" | grep NEEDED
MAX=$(objdump -T "$BIN" | grep -oE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' | sort -Vu | tail -1)
echo "$BIN max glibc symbol: $MAX"
test "$(printf '%s\nGLIBC_2.31\n' "$MAX" | sort -V | tail -1)" = "GLIBC_2.31"
# C++ runtime and libusb are baked in, not dynamic dependencies.
if objdump -p "$BIN" | grep -E 'NEEDED.*(libstdc\+\+|libgcc_s|libusb)'; then
echo "ERROR: unexpected dynamic dependency in $BIN" >&2
exit 1
fi
done
# Both video backends (and libdecor CSD support) must be compiled in.
CFG=$(find build/appimage -name SDL_build_config.h | head -1)
grep -q "#define SDL_VIDEO_DRIVER_WAYLAND 1" "$CFG"
Expand All @@ -147,15 +151,20 @@ jobs:
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
"$APPDIR/apprun-hooks"
cp build/appimage/labrador "$APPDIR/usr/bin/labrador"
# SDL_GetBasePath() resolves /proc/self/exe -> usr/bin, so assets live there.
# labrador-cli sits beside the GUI; the zz-labrador-cli.sh AppRun hook
# routes `<AppImage> cli ...` (or an ARGV0 of labrador-cli) to it.
cp build/appimage/cli/labrador-cli "$APPDIR/usr/bin/labrador-cli"
# SDL_GetBasePath() resolves /proc/self/exe -> usr/bin, so assets live
# there, shared by both binaries (the CLI's update-firmware flashes
# the same hex the GUI does).
cp -r assets/. "$APPDIR/usr/bin/assets/"
mkdir -p "$APPDIR/usr/bin/assets/firmware"
cp firmware/*.hex "$APPDIR/usr/bin/assets/firmware/"
cp packaging/69-labrador.rules "$APPDIR/usr/lib/udev/rules.d/"
cp packaging/espotek-labrador.desktop "$APPDIR/usr/share/applications/"
cp packaging/icon256.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/espotek-labrador.png"
cp packaging/linux/apprun-hooks/udev-helper.sh "$APPDIR/apprun-hooks/"
chmod +x "$APPDIR/apprun-hooks/udev-helper.sh"
cp packaging/linux/apprun-hooks/*.sh "$APPDIR/apprun-hooks/"
chmod +x "$APPDIR/apprun-hooks/"*.sh

- name: Build AppImage
run: |
Expand All @@ -172,6 +181,28 @@ jobs:
--output=appimage
mv Labrador*.AppImage "Labrador-for-Linux-${{ matrix.arch }}.AppImage"

# The CLI needs no display, so it can exercise the finished AppImage here
# (APPIMAGE_EXTRACT_AND_RUN: no FUSE in the container). update-firmware
# with no board must get as far as looking for one, which proves it found
# the bundled hex under usr/bin/assets.
- name: Smoke test labrador-cli in the AppImage
run: |
set -euxo pipefail
IMG="./Labrador_${{ matrix.arch }}.AppImage"
"$IMG" cli --help
"$IMG" cli help capture
"$IMG" cli help update-firmware
if "$IMG" cli update-firmware >cli.log 2>&1; then
echo "ERROR: update-firmware succeeded with no board attached" >&2
exit 1
fi
cat cli.log
grep -q "no Labrador board found" cli.log
# Invoked through a symlink named labrador-cli the AppImage runtime
# sets ARGV0 to that name and the hook routes to the CLI as well.
ln -sf "$IMG" labrador-cli
./labrador-cli --help | grep -q "command-line tools for the EspoTek Labrador"

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -257,10 +288,15 @@ jobs:
# SDL_GetBasePath() resolves /proc/self/exe to the real dir, so assets
# are found next to the binary at /usr/lib/labrador/assets.
install -m 0755 build/linux/labrador "$PKG/usr/lib/labrador/labrador"
# labrador-cli lives beside the GUI so both find the same assets
# (its update-firmware flashes the bundled hex); the /usr/bin
# symlink puts it on PATH.
install -m 0755 build/linux/cli/labrador-cli "$PKG/usr/lib/labrador/labrador-cli"
cp -r assets/. "$PKG/usr/lib/labrador/assets/"
mkdir -p "$PKG/usr/lib/labrador/assets/firmware"
cp firmware/*.hex "$PKG/usr/lib/labrador/assets/firmware/"
ln -s ../lib/labrador/labrador "$PKG/usr/bin/labrador"
ln -s ../lib/labrador/labrador-cli "$PKG/usr/bin/labrador-cli"
cp packaging/69-labrador.rules "$PKG/lib/udev/rules.d/"
cp packaging/espotek-labrador.desktop "$PKG/usr/share/applications/"
cp packaging/icon256.png "$PKG/usr/share/icons/hicolor/256x256/apps/espotek-labrador.png"
Expand All @@ -281,8 +317,9 @@ jobs:
Pi, or Android smartphone into a full-featured electronics lab bench,
complete with oscilloscope, signal generator, and more.
.
This package contains the SDL3/ImGui unified GUI plus the firmware images
it flashes to the board.
This package contains the SDL3/ImGui unified GUI, the headless
labrador-cli tool (triggered captures to CSV, firmware updates) and the
firmware images both flash to the board.
EOF

cat > "$PKG/DEBIAN/postinst" <<'EOF'
Expand All @@ -298,6 +335,27 @@ jobs:
dpkg-deb --root-owner-group --build "$PKG" "Labrador-for-Linux-${{ matrix.name }}.deb"
dpkg-deb --info "Labrador-for-Linux-${{ matrix.name }}.deb"

# Install the package into the build container (root, no udev, so the
# postinst's udevadm calls are skipped) and run the CLI from PATH.
# update-firmware with no board must get as far as looking for one,
# which proves /usr/bin/labrador-cli -> /usr/lib/labrador resolves the
# bundled hex.
- name: Smoke test labrador-cli from the installed .deb
run: |
set -euxo pipefail
dpkg -i "Labrador_${{ matrix.name }}.deb"
command -v labrador-cli
labrador-cli --help
labrador-cli help capture
labrador-cli help update-firmware
if labrador-cli update-firmware >cli.log 2>&1; then
echo "ERROR: update-firmware succeeded with no board attached" >&2
exit 1
fi
cat cli.log
grep -q "no Labrador board found" cli.log
test -f /usr/lib/labrador/assets/firmware/labrafirm_000C_03.hex

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
Loading
Loading