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
85 changes: 85 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Static validation plus per-language template smoke builds.
#
# What CI can and cannot verify: these jobs only run scripts/validate.py and
# build the hello-syscall templates. They CANNOT run the demos that need a
# KVM/libvirt VM, root privileges, or the LGTM stack — none of that exists on
# GitHub-hosted runners. The authoritative gate for the book remains a host
# run of scripts/test-all-examples.py against the real VM lab.

name: validate

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

# Needed for validate.py's Lua syntax pass; non-fatal so a transient
# apt outage doesn't block the whole workflow (validate.py skips the
# pass when lua5.4 is absent).
- name: Install Lua 5.4 (best effort)
run: sudo apt-get update && sudo apt-get install -y lua5.4 || echo "lua5.4 install failed; Lua syntax pass will be skipped"

- name: Run validate.py
run: python3 scripts/validate.py

smoke-go:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: examples/_template/go/go.mod

- name: Build Go template
working-directory: examples/_template/go
run: go build ./...

smoke-rust:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v5

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.97.1"

- name: Build Rust template
working-directory: examples/_template/rust
run: cargo build --release

smoke-cpp:
runs-on: ubuntu-latest
# Match the book's primary toolchain (Fedora GCC) rather than Ubuntu's.
container: fedora:44
steps:
- name: Check out repo
uses: actions/checkout@v5

- name: Install toolchain
run: dnf install -y gcc-c++ cmake ninja-build

- name: Build C++ template
working-directory: examples/_template/cpp
run: |
cmake --preset release
cmake --build --preset release
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ __pycache__/

# Reports
reports/
build-logs/
60 changes: 60 additions & 0 deletions examples/_infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Shared example infrastructure

Host-side services that some examples depend on. Today that is one thing: the
Grafana LGTM observability stack (Loki + Grafana + Tempo + Mimir, with a
built-in OpenTelemetry Collector), run as a single container via podman compose.

## LGTM stack

### Start / stop

From the repo root:

```sh
podman compose -f examples/_infra/compose-lgtm.yaml up -d
podman compose -f examples/_infra/compose-lgtm.yaml down
```

The container is named `lsp-lgtm` and has a healthcheck; wait for it to report
healthy before pointing examples at it (first boot can take ~30s):

```sh
podman inspect --format '{{.State.Health.Status}}' lsp-lgtm
```

### Ports

| Port | Purpose |
|------|---------|
| 3000 | Grafana UI (`http://localhost:3000`, anonymous admin) |
| 4317 | OTLP gRPC ingest |
| 4318 | OTLP HTTP ingest |

### How examples declare the dependency

An example that emits telemetry declares the stack in its manifest:

```yaml
requires: [lgtm]
```

The verification harness reads this and checks that the OTLP port is reachable
(via `expect_port_open`) before running the example, skipping (exit 77) when
the stack is not up rather than failing.

### Exporting from inside the lab VM

Examples deployed to the lab VM (`TARGET=...` with `deploy-to-vm.sh`) cannot
reach `localhost` on the host. From a libvirt guest on the default NAT network,
the host is reachable at the network's gateway IP — on this lab that is
`192.168.124.1` (libvirt's stock default is `192.168.122.1`; check yours with
`ip route show default` inside the guest). Pass the endpoint through
`OTEL_ENDPOINT`:

```sh
OTEL_ENDPOINT=http://192.168.124.1:4318 TARGET=systems-target ./demo.sh run
```

Verify the actual gateway from inside the guest with `ip route show default`
if the network is not the libvirt default. Ports 4317/4318 must be open to the
libvirt bridge on the host firewall (on Fedora: the `libvirt` firewalld zone).
39 changes: 39 additions & 0 deletions examples/_infra/compose-lgtm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# compose-lgtm.yaml
#
# Shared observability backend for the book's examples: the Grafana LGTM
# all-in-one image (Grafana + Loki + Tempo + Mimir + Prometheus + the
# OpenTelemetry Collector) as a single container on the host.
#
# Start/stop:
# podman compose -f examples/_infra/compose-lgtm.yaml up -d
# podman compose -f examples/_infra/compose-lgtm.yaml down
#
# Access after startup:
# Grafana UI: http://localhost:3000
# OTLP gRPC: localhost:4317
# OTLP HTTP: http://localhost:4318

services:
lgtm:
image: docker.io/grafana/otel-lgtm:0.8.1
container_name: lsp-lgtm
ports:
- "3000:3000" # Grafana UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
mem_limit: 2g
environment:
# Anonymous admin access keeps local demos friction-free. Never ship this.
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_DISABLE_LOGIN_FORM=true
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s # the all-in-one image takes a while on first boot

networks:
default:
name: lsp-observability
51 changes: 51 additions & 0 deletions examples/_template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# _template — hello-syscall

The seed for every runnable example in the book. It implements the same tiny
program three times — C++23, Go, and Rust — each printing exactly one line:

```
pid <PID> on <sysname> <release> (<machine>)
```

via `getpid(2)` + `uname(2)` equivalents, with each language's idiomatic error
handling (`std::expected` / wrapped errors via `%w` / `rustix`).

## Layout

```
_template/
├── demo.sh # dispatcher: ./demo.sh [cpp|go|rust|all|build] [args...]
├── verify.lua # automated check driven by CI
├── cpp/ # CMake preset build, demo.sh
├── go/ # go build, demo.sh
└── rust/ # cargo build, demo.sh
```

## The demo contract

Every language directory has a `demo.sh` with the same interface, identical
across the whole book:

- `./demo.sh` — build then run
- `./demo.sh build` — build only
- `./demo.sh run [args]` — run the built binary
- With env `TARGET` set, `run` deploys the binary to that lab VM via
`scripts/lab/deploy-to-vm.sh "$TARGET" <binary> -- [args]` instead of
running locally.

The top-level `demo.sh` here is only a dispatcher: `./demo.sh cpp run` execs
`cpp/demo.sh run`; bare `./demo.sh` (or `all`) builds and runs all three.

## Verification

`verify.lua` is run from this directory with `LSP_LANG` set to one language at
a time. It uses `scripts/lib/checks.lua` to run `./demo.sh $LSP_LANG run` and
asserts the output matches `pid %d+ on Linux`. Exit 0 = pass, 1 = fail,
77 = skip.

## Mapping to a chapter

Each chapter's example is a copy of this template (`scripts/new-example.sh
NN-slug`) with the program body replaced by that chapter's code. Keep the
demo contract and `verify.lua` shape intact so CI and the manifest
(`examples/manifest.yaml`) keep working.
8 changes: 8 additions & 0 deletions examples/_template/cpp/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
BasedOnStyle: LLVM
Language: Cpp
Standard: c++20
IndentWidth: 4
ColumnLimit: 100
PointerAlignment: Left
AllowShortFunctionsOnASingleLine: Inline
11 changes: 11 additions & 0 deletions examples/_template/cpp/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
Checks: >
bugprone-*,
modernize-*,
performance-*,
readability-*,
-modernize-use-trailing-return-type,
-readability-identifier-length
WarningsAsErrors: ''
HeaderFilterRegex: '.*'
FormatStyle: file
8 changes: 8 additions & 0 deletions examples/_template/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.25)
project(template_hello CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(app src/main.cpp)
62 changes: 62 additions & 0 deletions examples/_template/cpp/CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
"configurePresets": [
{
"name": "_base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"inherits": "_base",
"displayName": "Release (-O2 with debug info)",
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }
},
{
"name": "debug",
"inherits": "_base",
"displayName": "Debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
},
{
"name": "asan",
"inherits": "_base",
"displayName": "ASan + UBSan instrumented build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS": "-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined",
"CMAKE_EXE_LINKER_FLAGS": "-fsanitize=address,undefined"
}
},
{
"name": "release-clang",
"inherits": "release",
"displayName": "Release (clang)",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "debug-clang",
"inherits": "debug",
"displayName": "Debug (clang)",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
}
],
"buildPresets": [
{ "name": "release", "configurePreset": "release" },
{ "name": "debug", "configurePreset": "debug" },
{ "name": "asan", "configurePreset": "asan" },
{ "name": "release-clang", "configurePreset": "release-clang" },
{ "name": "debug-clang", "configurePreset": "debug-clang" }
]
}
40 changes: 40 additions & 0 deletions examples/_template/cpp/demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")"
REPO_ROOT="$(cd ../../.. && pwd)"
BIN=build/release/app

build() {
cmake --preset release
cmake --build --preset release
}

run() {
if [[ ! -x "$BIN" ]]; then
build
fi
if [[ -n "${TARGET:-}" ]]; then
"$REPO_ROOT/scripts/lab/deploy-to-vm.sh" "$TARGET" "$BIN" -- "$@"
else
"./$BIN" "$@"
fi
}

case "${1:-}" in
build)
build
;;
run)
shift
run "$@"
;;
"")
build
run
;;
*)
echo "usage: $0 [build|run [args]]" >&2
exit 2
;;
esac
Loading
Loading