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
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions cmd/dbgp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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=<tag>".
var version = "dev"

// listFlag collects a repeatable string flag.
type listFlag []string

Expand All @@ -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)")
Expand All @@ -43,6 +48,10 @@ func main() {
flag.PrintDefaults()
}
flag.Parse()
if *showVersion {
fmt.Println("dbgp", version)
return
}

pathMap, err := parsePathMap(maps)
if err != nil {
Expand Down
3 changes: 0 additions & 3 deletions cmd/dbgp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions cmd/dbgp/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading