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
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CI

on:
# Run on pushes to main (e.g. merges) and on every PR. Limiting push to main
# avoids double runs on branches that also have an open PR.
push:
branches: [main]
pull_request:

# Cancel superseded runs for the same branch/PR to save resources.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
lint:
name: Lint (gofmt + vet)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Check formatting
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "The following files are not gofmt-formatted:"
echo "$unformatted"
echo "Run: gofmt -w ."
exit 1
fi
- name: go vet
run: go vet ./...

test:
name: Build & test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build
run: go build ./...
- name: Test
run: go test -race ./...
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Built binary
/php-debugger
/php-debugger.exe

# Go build/test artifacts
*.test
*.out
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/php-debugger/installer

go 1.25.6

require github.com/spf13/cobra v1.10.2

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42 changes: 42 additions & 0 deletions internal/cli/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cli

import "github.com/spf13/cobra"

// installOptions holds flags specific to the install command.
type installOptions struct {
// PHPVersion selects the PHP version to install (e.g. "8.3"); empty means
// the latest available in the release. Interpreter installs only.
PHPVersion string
// ZTS installs the thread-safe build; default is non-thread-safe.
// Interpreter installs only.
ZTS bool
// ExtensionOnly installs just the debugger extension into the current PHP
// instead of a full interpreter.
ExtensionOnly bool
}

func newInstallCmd() *cobra.Command {
opts := &installOptions{}

cmd := &cobra.Command{
Use: "install",
Short: "Install the PHP debugger interpreter (default) or just the extension",
Long: "Install a self-contained PHP interpreter with the debugger compiled in\n" +
"(default), or, with --extension-only, install just the debugger extension\n" +
"into the currently active PHP.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return errNotImplemented("install")
},
}

f := cmd.Flags()
f.StringVarP(&opts.PHPVersion, "php", "p", "",
"PHP version to install, e.g. 8.3 (default: latest available; interpreter only)")
f.BoolVarP(&opts.ZTS, "zts", "z", false,
"install the thread-safe (ZTS) build; default is non-thread-safe (interpreter only)")
f.BoolVarP(&opts.ExtensionOnly, "extension-only", "e", false,
"install only the debugger extension into the current PHP")

return cmd
}
64 changes: 64 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cli

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

// GlobalOptions holds flags shared by every subcommand.
type GlobalOptions struct {
// User selects the no-sudo, per-user install scope instead of the default
// system-wide scope (/opt on Linux/macOS, Program Files on Windows).
User bool
// Yes assumes "yes" for every interactive confirmation (for CI).
Yes bool
// Verbose enables extra diagnostic output.
Verbose bool
}

var globalOpts GlobalOptions

func newRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "php-debugger",
Short: "Install and manage the PHP debugger",
Long: "php-debugger installs the PHP debugger from the php-debugger/php-debugger\n" +
"GitHub releases: either a self-contained PHP interpreter with the debugger\n" +
"compiled in (default), or the debugger extension for an existing PHP.",
SilenceUsage: true,
SilenceErrors: true,
}

pf := rootCmd.PersistentFlags()
pf.BoolVarP(&globalOpts.User, "user", "u", false,
"install into a per-user directory (no sudo); default is system-wide")
pf.BoolVarP(&globalOpts.Yes, "yes", "y", false,
"assume yes for all prompts (non-interactive)")
pf.BoolVarP(&globalOpts.Verbose, "verbose", "V", false,
"enable verbose output")

rootCmd.AddCommand(
newInstallCmd(),
newUpdateCmd(),
newUninstallCmd(),
newSwitchCmd(),
)

return rootCmd
}

// Execute runs the root command and exits the process with an appropriate code.
func Execute() {
if err := newRootCmd().Execute(); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
}

// errNotImplemented is returned by subcommand stubs until they are built out in
// later steps of the roadmap.
func errNotImplemented(name string) error {
return fmt.Errorf("%q is not implemented yet", name)
}
30 changes: 30 additions & 0 deletions internal/cli/switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cli

import "github.com/spf13/cobra"

// switchOptions holds flags specific to the switch command.
type switchOptions struct {
// ZTS selects the thread-safe variant of the target version.
ZTS bool
}

func newSwitchCmd() *cobra.Command {
opts := &switchOptions{}

cmd := &cobra.Command{
Use: "switch <version>",
Short: "Switch the active PHP version (installing it first if needed)",
Long: "Point the active PHP symlink at the given version (and optionally its\n" +
"thread-safe variant). If that variant is not installed yet, it is installed\n" +
"first.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return errNotImplemented("switch")
},
}

cmd.Flags().BoolVarP(&opts.ZTS, "zts", "z", false,
"select the thread-safe (ZTS) variant of the target version")

return cmd
}
34 changes: 34 additions & 0 deletions internal/cli/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cli

import "github.com/spf13/cobra"

// uninstallOptions holds flags specific to the uninstall command.
type uninstallOptions struct {
// Extension uninstalls the debugger extension.
Extension bool
// Interpreter uninstalls a debugger interpreter (optionally a specific
// version given as a positional argument).
Interpreter bool
}

func newUninstallCmd() *cobra.Command {
opts := &uninstallOptions{}

cmd := &cobra.Command{
Use: "uninstall [version]",
Short: "Uninstall the interpreter or extension, restoring any backup",
Long: "Remove a debugger interpreter (optionally a specific version) or the\n" +
"debugger extension. If a backup of a previously replaced interpreter exists,\n" +
"it is restored.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return errNotImplemented("uninstall")
},
}

f := cmd.Flags()
f.BoolVarP(&opts.Extension, "extension", "e", false, "uninstall the debugger extension")
f.BoolVarP(&opts.Interpreter, "interpreter", "i", false, "uninstall the debugger interpreter")

return cmd
}
33 changes: 33 additions & 0 deletions internal/cli/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cli

import "github.com/spf13/cobra"

// updateOptions holds flags specific to the update command.
type updateOptions struct {
// Extension updates the installed debugger extension.
Extension bool
// Interpreter updates the active debugger interpreter.
Interpreter bool
}

func newUpdateCmd() *cobra.Command {
opts := &updateOptions{}

cmd := &cobra.Command{
Use: "update",
Short: "Update the installed interpreter or extension to the latest release",
Long: "Re-install the currently active interpreter (or the extension) against the\n" +
"latest release. Use --extension or --interpreter to disambiguate when both\n" +
"are installed.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return errNotImplemented("update")
},
}

f := cmd.Flags()
f.BoolVarP(&opts.Extension, "extension", "e", false, "update the debugger extension")
f.BoolVarP(&opts.Interpreter, "interpreter", "i", false, "update the debugger interpreter")

return cmd
}
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Command php-debugger installs and manages the PHP debugger from the
// php-debugger/php-debugger GitHub releases.
package main

import "github.com/php-debugger/installer/internal/cli"

func main() {
cli.Execute()
}
Loading