From 2e2696ad57207cf33ec566f659d6803f8da13e15 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Thu, 15 Oct 2026 19:00:45 +0200 Subject: [PATCH] Build release binaries for Linux, macOS and Windows --- .github/workflows/release.yml | 57 +++++++++++++++++++++++++++++++++++ README.md | 4 +-- cmd/dbgp/main.go | 9 ++++++ cmd/dbgp/mcp.go | 3 -- cmd/dbgp/mcp_test.go | 20 ++++++++++-- 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6826daa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release + +on: + release: + # Runs when a release is published in GitHub (draft -> published, or a new + # published release). The release already exists, so we just attach binaries. + types: [published] + +permissions: + contents: write # needed to upload release assets + +jobs: + release: + name: Build & attach binaries + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + + - name: Build and package all targets + env: + TAG: ${{ github.event.release.tag_name }} + run: | + mkdir -p dist build + ver="${TAG#v}" # strip a leading "v" if present + ldflags="-s -w -X main.version=${ver}" + # Windows-on-ARM runs x64 under emulation, so one Windows build suffices. + for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do + goos="${target%/*}"; arch="${target#*/}" + # Asset OS name: darwin -> macos. + osname="$goos"; [ "$goos" = "darwin" ] && osname="macos" + bin="dbgp"; [ "$goos" = "windows" ] && bin="dbgp.exe" + + echo "building $osname/$arch" + CGO_ENABLED=0 GOOS="$goos" GOARCH="$arch" \ + go build -trimpath -ldflags "$ldflags" -o "build/$bin" ./cmd/dbgp + chmod +x "build/$bin" + + # Package into an archive so the executable bit survives download + # (raw GitHub release assets lose Unix permissions). + base="dbgp-${osname}-${arch}" + if [ "$goos" = "windows" ]; then + (cd build && zip -q "../dist/${base}.zip" "$bin") + else + tar -C build -czf "dist/${base}.tar.gz" "$bin" + fi + rm -f "build/$bin" + done + ls -la dist + + - name: Attach binaries to the release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$TAG" dist/* --clobber diff --git a/README.md b/README.md index c7b10d1..e8f9742 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,14 @@ notes, _ := sess.Notifications(0) // PHP warnings and other notifications ## Command-line debugger -`cmd/dbgp` is an interactive debugger built on the library: +`cmd/dbgp` is an interactive debugger built on the library. Each [release](https://github.com/php-debugger/dbgp-client/releases) has it built for Linux and macOS (amd64 and arm64) and Windows (amd64); or install it with Go: ```sh go install github.com/php-debugger/dbgp-client/cmd/dbgp@latest dbgp -break src/Controller/HomeController.php:25 ``` -Then start PHP with debugging enabled (e.g. `php -dxdebug.mode=debug -dxdebug.start_with_request=yes app.php`). Sessions are announced as they connect, and commands that need one wait for it, as `run` and the step commands wait for the script to stop (Ctrl-C cancels a wait). It listens from the start; `unlisten` refuses new PHP connections, so scripts run undebugged, and `listen` accepts them again. `map LOCAL=REMOTE` adds a path mapping while debugging (also applied to connected sessions) and `maps` lists them. Type `help` for commands such as `run`, `next`, `step`, `stack`, `vars`, `print $user["name"]`, `eval`, `output` and `notes`. On a terminal it has line editing, tab completion and history (kept in `~/.dbgp_history`; change it with `-history`, or pass `-history ""` to disable it). Other options: `-addr`, `-idekey`, `-map LOCAL=REMOTE` for PHP in a container or on a server, and `-break FILE:LINE`. Commands can also be piped in on stdin. +Then start PHP with debugging enabled (e.g. `php -dxdebug.mode=debug -dxdebug.start_with_request=yes app.php`). Sessions are announced as they connect, and commands that need one wait for it, as `run` and the step commands wait for the script to stop (Ctrl-C cancels a wait). It listens from the start; `unlisten` refuses new PHP connections, so scripts run undebugged, and `listen` accepts them again. `map LOCAL=REMOTE` adds a path mapping while debugging (also applied to connected sessions) and `maps` lists them. Type `help` for commands such as `run`, `next`, `step`, `stack`, `vars`, `print $user["name"]`, `eval`, `output` and `notes`. On a terminal it has line editing, tab completion and history (kept in `~/.dbgp_history`; change it with `-history`, or pass `-history ""` to disable it). Other options: `-version`, `-addr`, `-idekey`, `-map LOCAL=REMOTE` for PHP in a container or on a server, and `-break FILE:LINE`. Commands can also be piped in on stdin. ## MCP server for AI agents diff --git a/cmd/dbgp/main.go b/cmd/dbgp/main.go index 2035fee..935cbc9 100644 --- a/cmd/dbgp/main.go +++ b/cmd/dbgp/main.go @@ -18,6 +18,10 @@ import ( dbgp "github.com/php-debugger/dbgp-client" ) +// version is "dev" for local builds; release builds set it to the release +// tag with -ldflags "-X main.version=". +var version = "dev" + // listFlag collects a repeatable string flag. type listFlag []string @@ -32,6 +36,7 @@ func main() { addr := flag.String("addr", dbgp.DefaultAddr, "address to listen on for PHP connections") idekey := flag.String("idekey", "", "accept only sessions with this IDE key") history := flag.String("history", defaultHistoryFile(), "`file` to keep command history in; empty disables it") + showVersion := flag.Bool("version", false, "print the version and exit") var maps, breaks listFlag flag.Var(&maps, "map", "path mapping `LOCAL=REMOTE` for PHP in a container or on a server (repeatable)") flag.Var(&breaks, "break", "breakpoint at `FILE:LINE` (repeatable)") @@ -43,6 +48,10 @@ func main() { flag.PrintDefaults() } flag.Parse() + if *showVersion { + fmt.Println("dbgp", version) + return + } pathMap, err := parsePathMap(maps) if err != nil { diff --git a/cmd/dbgp/mcp.go b/cmd/dbgp/mcp.go index f21439c..50e28c3 100644 --- a/cmd/dbgp/mcp.go +++ b/cmd/dbgp/mcp.go @@ -18,9 +18,6 @@ import ( dbgp "github.com/php-debugger/dbgp-client" ) -// version identifies this build to MCP clients. -const version = "dev" - // mcpInstructions are sent to MCP clients when they connect. const mcpInstructions = `dbgp debugs PHP scripts and requests through PHP Debugger or Xdebug. It does not listen for PHP connections until the listen tool is called, so diff --git a/cmd/dbgp/mcp_test.go b/cmd/dbgp/mcp_test.go index c3cdf53..5555558 100644 --- a/cmd/dbgp/mcp_test.go +++ b/cmd/dbgp/mcp_test.go @@ -239,18 +239,34 @@ func TestMCPStatusShowsSession(t *testing.T) { // The built binary in MCP mode, driven over stdin and stdout. // buildBinary builds dbgp into a temporary directory and returns its path. -func buildBinary(t *testing.T) string { +func buildBinary(t *testing.T, args ...string) string { t.Helper() if testing.Short() { t.Skip("builds the binary") } bin := filepath.Join(t.TempDir(), "dbgp") - if out, err := exec.Command("go", "build", "-o", bin, ".").CombinedOutput(); err != nil { + if out, err := exec.Command("go", append(append([]string{"build", "-o", bin}, args...), ".")...).CombinedOutput(); err != nil { t.Fatalf("go build: %v\n%s", err, out) } return bin } +// Release builds set the version as the release workflow does. +func TestVersion(t *testing.T) { + for _, tc := range []struct { + args []string + want string + }{ + {nil, "dbgp dev\n"}, + {[]string{"-ldflags", "-X main.version=1.2.3"}, "dbgp 1.2.3\n"}, + } { + out, err := exec.Command(buildBinary(t, tc.args...), "-version").CombinedOutput() + if err != nil || string(out) != tc.want { + t.Errorf("%v: -version = %q, %v; want %q", tc.args, out, err, tc.want) + } + } +} + func TestMCPBinary(t *testing.T) { bin := buildBinary(t) daemonEnv(t)